diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 5a7e990c..2eecbe82 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,2 @@ github: mswjs -open_collective: mswjs +open_collective: mswjs \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1d46414e..3ca48c44 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,32 +4,37 @@ on: push: branches: [main] pull_request: - branches: [main] - workflow_dispatch: jobs: - build: + test: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 + + - name: Set up pnpm + uses: pnpm/action-setup@v4 + with: + version: 9.15.0 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 24 + cache: 'pnpm' - name: Install dependencies - run: yarn install --frozen-lockfile + run: pnpm install - - name: Environment (versions) - run: | - echo "node: $(node -v)" - echo "npm: $(npm -v)" - echo "typescript: $(npm ls typescript)" - echo "tsc: $(tsc -v)" + - name: Install browsers + run: pnpm exec playwright install chromium --with-deps - name: Build - run: yarn build + run: pnpm build - - name: Tests - run: yarn test + - name: Tests (Node.js) + run: pnpm test:node - - name: Tests (typings) - run: yarn test:ts + - name: Tests (browser) + run: pnpm test:browser diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 538cf671..f6bc65f7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,29 +3,55 @@ name: release on: schedule: - cron: '0 23 * * *' - workflow_dispatch: jobs: - release: + publish: runs-on: ubuntu-latest + permissions: + contents: read + id-token: write steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: fetch-depth: 0 token: ${{ secrets.GH_ADMIN_TOKEN }} - - name: Setup Git + - name: Set up pnpm + uses: pnpm/action-setup@v4 + with: + version: 9.15.0 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 24 + cache: 'pnpm' + always-auth: true + registry-url: https://registry.npmjs.org + + - name: Set up Git run: | - git config --local user.name "kettanaito" + git config --local user.name "Artem Zakharchenko" git config --local user.email "kettanaito@gmail.com" - name: Install dependencies - run: yarn install --frozen-lockfile + run: pnpm install + + - name: Install browsers + run: pnpm exec playwright install chromium --with-deps + + - name: Build + run: pnpm build + + - name: Tests (Node.js) + run: pnpm test:node + + - name: Tests (browser) + run: pnpm test:browser - - name: Release - run: yarn release + - name: Publish + run: pnpm release env: GITHUB_TOKEN: ${{ secrets.GH_ADMIN_TOKEN }} - NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore index 611ff96e..6ad7ad0a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +*.DS_Store node_modules +build lib -*.log \ No newline at end of file +dist +test-results +./tests/.tmp/** \ No newline at end of file diff --git a/.node-version b/.node-version new file mode 100644 index 00000000..54c65116 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +v24 diff --git a/.prettierrc b/.prettierrc.json similarity index 50% rename from .prettierrc rename to .prettierrc.json index ba44425d..65854ba4 100644 --- a/.prettierrc +++ b/.prettierrc.json @@ -1,6 +1,6 @@ { "semi": false, - "singleQuote": true, "arrowParens": "always", - "trailingComma": "all" + "trailingComma": "all", + "singleQuote": true } diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 3662b370..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "typescript.tsdk": "node_modules/typescript/lib" -} \ No newline at end of file diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..f1314ab0 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2020–present Artem Zakharchenko + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/MIGRATING.md b/MIGRATING.md new file mode 100644 index 00000000..d38da4cf --- /dev/null +++ b/MIGRATING.md @@ -0,0 +1,148 @@ +# Migrating + +## v0 → v1 + +Version 1.0 is a huge overhaul of the library to address some of the long-standing design issues and missing features. Please take your time to read through the [updated README](./README.md) to get familiar with the new concepts and APIs. + +When migrating from v0.x.x, please consult the guide below regarding the breaking changes to the library. + +### Terminology + +A few changes in the terminology to consider to make this migration guide easier to parse: + +- Model → Collection; +- Entry → Record; +- Relationship → Relation. + +### Package name + +The library has been migrated to a new package name—`@msw/data`. + +```diff +-npm i @mswjs/data ++npm i @msw/data +``` + +### Deprecated: `factory` + +The model factory has been deprecated in favor of granular collections of data. + +```diff +-const db = factory({ user: {} }) ++const users = new Collection({ schema }) +``` + +### Describing data + +The proprietary syntax of describing data has been deprecated in favor of the [Standard Schema](https://standardschema.dev/) for describing your data. + +```diff +-factory({ +- firstName: () => 'John', +- lastName: () => 'Maverick' +-}) ++new Collection({ ++ schema: z.object({ ++ firstName: z.string(), ++ lastName: z.string() ++ }) ++}) +``` + +> Although the example above is using [Zod](https://zod.dev/), you can use any Standard Schema-compatible schema library. + +### Deprecated: `primaryKey` + +The concept of a primary key has been deprecated entirely. You do not have to provide primary keys when describing your collections. You can query records by any properties. + +### Default values + +You can provide default values to the properties of your collection using the respective syntax of your schema library of choice. For example, here's how you list default values in Zod: + +```ts +const users = new Collection({ + schema: z.object({ + subscribed: z.boolean().default(false) + }) +}) + +await users.create() // { subscribed: false } +``` + +### Deprecated: `nullable` + +The `nullable` utility has been deprecated. Use your schema library of choice to describe nullable properties. + +### Deprecated: operators + +Operators like `equals`, `in`, `lgt`, etc. have been deprecated in favor of _queries_. You can express conditions for your records via literal values as well as function predicates. + +```ts +// Find the first user with "id" equal to 1. +users.findFirst(q => q.where({ id: 1 })) + +// Find all users whose last name is longer than 5 letters. +users.findMany( + q => q.where({ lastName: lastName => lastName.length > 5 }) +) +``` + +> Learn more about [Querying](./README.md#querying). + +### Relations + +The `oneOf` and `manyOf` utilities has been deprecated. Use the `.defineRelations()` method on your collections to define relations between collections. + +```ts +const userSchema = z.object({ + id: z.number(), + get posts() { + return z.array(postSchema) + } +}) +const postSchema = z.object({ + title: z.string(), + get author() { + return userSchema + } +}) + +const users = new Collection({ schema: userSchema }) +const posts = new Collection({ schema: postSchema }) + +users.defineRelations(({ many }) => ({ + posts: many(posts) +})) + +posts.defineRelations(({ one }) => ({ + author: one(users) +})) +``` + +> Learn more about [Relations](./README.md#relations). + +### Collocated updates + +You can now collocate updates of owner and foreign records in a relation by simply changing the foreign record's values as a part of the owner update. + +```ts +const posts = new Collection({ schema: postSchema }) +const revisions = new Collection({ schema: revisionSchema }) + +posts.defineRelations(({ one }) => ({ + revision: one(revisions) +})) + +await posts.update(q => q.where({ id: 'post-1' }), { + data(post) { + post.title = 'Renamed post' + post.revision.updatedAt = Date.now() + } +}) +``` + +> The library will automatically translte the `updateAt` change of the reference revision into an implicit update. + +### Deprecated: `.toHandlers()` + +The `.toHandlers()` method has been deprecated. Generating request handlers is no longer a responsibility of this library. Instead, it should be possible via [Source](https://github.com/mswjs/source) (follow [this issue](https://github.com/mswjs/source/issues/80) for progress). diff --git a/README.md b/README.md index cd5e5f23..d48770b9 100644 --- a/README.md +++ b/README.md @@ -1,1196 +1,848 @@ +[standard-schema]: https://standardschema.dev/ +

- Data library logo + Data logo

+

@msw/data

+

Data querying library for testing JavaScript applications.

-

@mswjs/data

+## Motivation -

Data modeling and relation library for testing JavaScript applications.

-
+This library exists to help developers model and query data when testing and developing their applications. It acts as a convenient way of creating schema-based fixtures and querying them with a familiar ORM-inspired syntax. It can be used standalone or in conjuncture with [Mock Service Worker](https://mswjs.io) for seamless mocking experience both on the network and the data layers. -## Motivation +## Features -When testing API interactions you often need to mock data. Instead of keeping a hard-coded set of fixtures, this library provides you with must-have tools for data-driven API mocking: +- Relies on [Standard Schema][standard-schema] instead of inventing a proprietary modeling syntax you have to learn. You can use any Standard Schema-compliant object modeling library to describe your data, like Zod, ArkType, Valibot, yup, and many others. +- Full runtime and type-safety. +- Provides a powerful [querying syntax](#querying) (inspired by Prisma); +- Supports [relations](#relations) for database-like behaviors (inspired by Drizzle); +- Supports extensions (including custom extensions) for things like cross-tab collection synchronization or record persistence. -- An intuitive interface to model data; -- The ability to create relationships between models; -- The ability to query data in a manner similar to an actual database. +--- ## Getting started ### Install -```bash -$ npm install @mswjs/data --save-dev -# or -$ yarn add @mswjs/data --save-dev ``` - -### Describe data - -With this library, you're modeling data using the `factory` function. That function accepts an object where each key represents a _model name_ and the values are _model definitions_. A model definition is an object where the keys represent model properties and the values are value getters. - -```js -// src/mocks/db.js -import { factory, primaryKey } from '@mswjs/data' - -export const db = factory({ - // Create a "user" model, - user: { - // ...with these properties and value getters. - id: primaryKey(() => 'abc-123'), - firstName: () => 'John', - lastName: () => 'Maverick', - }, -}) +npm i @msw/data --save-dev ``` -> See the [Recipes](#recipes) for more guidelines on data modeling. +### Create collection -Throughout this document native JavaScript constructors (i.e. String, Number) will be used as values getters for the models, as they both create a value and define its type. In practice, you may consider using value generators or tools like [Faker](#usage-with-faker) for value getters. +You start by defining a data _collection_. -#### Using the primary key - -Each model **must have a primary key**. That is a root-level property representing the model's identity. Think of it as an "id" column for a particular table in a database. - -Declare a primary key by using the `primaryKey` function: - -```js -import { factory, primaryKey } from '@mswjs/data' +```ts +import { Collection } from '@msw/data' +import { z } from 'zod' -factory({ - user: { - id: primaryKey(String), - }, +const users = new Collection({ + schema: z.object({ + id: z.number(), + name: z.string(), + }), }) ``` -In the example above, the `id` is the primary key for the `user` model. This means that whenever a `user` is created it must have the `id` property that equals a unique `String`. Any property can be marked as a primary key, it doesn't have to be named "id". +> Above, I'm using [Zod](https://zod.dev/) to describe the `schema` for my users collection. You can use whichever [Standard Schema][standard-schema] compliant library of your choice instead. -Just like regular model properties, the primary key accepts a getter function that you can use to generate its value when creating entities: +### Seed collection -```js -import { datatype } from '@faker-js/faker' +Next, let's put some values into our collection. Those values are called _records_ and you can create individual records via the `.create()` method or create a bunch of them with `.createMany()`. -factory({ - user: { - id: primaryKey(datatype.uuid), - }, -}) +```ts +await users.create({ id: 1, name: 'John' }) + +await users.createMany(5, (index) => ({ + id: index + 1, + name: faker.person.firstName(), +})) ``` -> Each time a new `user` is created, its `user.id` property is seeded with the value returned from the `datatype.uuid` function call. +> Combine `.createMany()` with tools like [Faker](https://fakerjs.dev/) for random values in your records. -Once your data is modeled, you can use [Model methods](#model-methods) to interact with it (create/update/delete). Apart from serving as interactive, queryable fixtures, you can also [integrate your data models into API mocks](#usage-with-api-mocks) to supercharge your prototyping/testing workflow. +### Use collection -## API +From this point on, you can use your `users` collection for anything data-related. You can create more records, query them, define relations to other collections, update and delete records, etc. Learn more you can do with the library in the documentation below. Good luck! -- [`factory`](#factory) -- [`primaryKey`](#primaryKey) -- [`nullable`](#nullable) -- [`oneOf`](#oneOf) -- [`manyOf`](#manyOf) -- [`drop`](#drop) +--- -### `factory` +## Querying -The `factory` function is used to model a database. It accepts a _model dictionary_ and returns an API to interact with the described models. +### Query syntax -```js -import { factory, primaryKey } from '@mswjs/data' +Whenever you have to target a record(s), you construct a _query_. A query acts as a predicate that a record must match in order to be targeted. The most basic query is that describing expected values of the record's properties: -const db = factory({ - user: { - id: primaryKey(String), - firstName: String - age: Number - } -}) +```ts +users.findFirst((q) => q.where({ name: 'John' })) ``` -> Learn more about the [Model methods](#model-methods) and how you can interact with the described models. - -Each `factory` call encapsulates an in-memory database instance that holds the respective models. It's possible to create multiple database instances by calling `factory` multiple times. The entities and relationships, however, are not shared between different database instances. +> Above, we are defining a query using the `q` builder that targets the first user whose `name` property equals to `'John'`. -### `primaryKey` +Additionally, any property in a query can be expanded into a function that accepts the value and returns a boolean, indicating whether the record matches: -Marks the property of a model as a primary key. - -```js -import { factory, primaryKey } from '@mswjs/data' - -const db = factory({ - user: { - id: primaryKey(String), - }, -}) - -// Create a new "user" with the primary key "id" equal to "user-1". -db.user.create({ id: 'user-1' }) +```ts +users.findFirst((q) => + q.where({ + name: (name) => name.startsWith('John'), + }), +) ``` -Primary key must be unique for each entity and is used as the identifier to query a particular entity. - -### `nullable` +> This query matches the first user whose `name` starts with `'John'`. Use functions as predicates to express more advanced logic in your queries. -Marks the current model property as nullable. +Query functions are supported at any level of nesting, including the top-level record itself. -```js -import { factory, primaryKey, nullable } from '@mswjs/data' +```ts +users.findFirst((q) => q.where((user) => user.posts.length > 0)) -factory({ - user: { - id: primaryKey(String) - // "user.title" is a nullable property. - title: nullable(String) - } -}) +users.findFirst((q) => + q.where({ + address: { + street: (street) => street !== 'Baker st.', + }, + }), +) ``` -> Learn more how to work with [Nullable properties](#nullable-properties). +#### Logical operators -### `oneOf` +You can build complex queries via `.or()` and `.and()` logical operators exposed through the query builder. For example, here's a query that matches all users who have posts _or_ are editors: -Creates a `*-to-one` relationship with another model. +```ts +users.findMany((q) => + q.where({ posts: (posts) => posts.length > 0 }).or({ role: 'editor' }), +) +``` -```js -import { factory, primaryKey, oneOf } from '@mswjs/data' +If you prefer functional composition over method chaining, you can wrap predicates in `q.or()` and `q.and()` instead. Both syntaxes result in the same query. -factory({ - user: { - id: primaryKey(String), - role: oneOf('userGroup'), - }, - userGroup: { - name: primaryKey(String), - }, -}) +```ts +users.findMany((q) => + q.or( + q.where({ posts: (posts) => posts.length > 0 }), + q.where({ role: 'editor' }), + ), +) ``` -> Learn more about [Modeling relationships](#model-relationships). +## Pagination -### `manyOf` +This library supports offset and cursor-based pagination. -Creates a `*-to-many` relationship with another model. +### Offset-based pagination -```js -import { factory, primaryKey, manyOf } from '@mswjs/data' +Provide the `take` property onto the options object of any bulk operation method, like `.findMany()`, `.updateMany()`, or `.deleteMany()`, to limit the number of results returned by the query. -factory({ - user: { - id: primaryKey(String), - publications: manyOf('post'), - }, - post: { - id: primaryKey(String), - title: String, +```ts +const users = new Collection({ schema }) + +users.findMany( + (q) => q.where({ email: (email) => email.includes('@google.com') }), + { + // Return the first 5 matching records. + take: 5, }, -}) +) ``` -> Learn more about [Modeling relationships](#model-relationships). - -### `drop` - -Deletes all entities in the given database instance. +You can also skip the number of first matching results by providing the `skip` property: -```js -import { factory, drop } from '@mswjs/data' - -const db = factory(...models) +```ts +const users = new Collection({ schema }) -drop(db) +users.findMany( + (q) => q.where({ email: (email) => email.includes('@google.com') }), + { + // Skip the first 10 matching records. + skip: 10, + // And return the next 5. + take: 5, + }, +) ``` -## Model methods - -Each model has the following methods: +The negative value for `take` is also supported to have backward pagination: -- [`create()`](#create) -- [`findFirst()`](#findFirst) -- [`findMany()`](#findMany) -- [`count()`](#count) -- [`getAll()`](#getAll) -- [`update()`](#update) -- [`updateMany()`](#updateMany) -- [`delete()`](#delete) -- [`deleteMany()`](#deleteMany) -- [`toHandlers()`](#toHandlers) +```ts +users.findMany( + (q) => q.where({ email: (email) => email.includes('@google.com') }), + { + take: -5, + }, +) +```` -### `create` +### Cursor-based pagination -Creates an entity for the model. +Provide a reference to the record of the same collection as the `cursor` property for cursor-based pagination. -```js -const user = db.user.create() -``` - -When called without arguments, `.create()` will populate the entity properties using the getter functions you've specified in the model definition. +```ts +const users = new Collection({ schema }) -You can also provide a partial initial values when creating an entity: +const john = users.findFirst((q) => q.where({ name: 'John' })) -```js -const user = db.user.create({ - firstName: 'John', +users.findMany((q) => q.where({ subscribed: true }), { + cursor: john, + take: 5, }) ``` -> Note that all model properties _are optional_, including [relational properties](#model-relationships). +## Sorting -### `findFirst` +You can sort the results of bulk operations, like `.findMany()`, `.updateMany()`, and `.deleteMany()`, by providing the `orderBy` property in that operation's options. -Returns the first entity that satisfies the given query. - -```js -const user = db.user.findFirst({ - where: { - id: { - equals: 'abc-123', - }, - }, +```ts +// Find all users whose name starts with "J" +// and return them sorted by their `name`. +users.findMany((q) => q.where({ name: (name) => name.startsWith('J') }), { + orderBy: { name: 'asc' }, }) ``` -### `findMany` +You can sort by multiple keys by listing them in the `orderBy` object: -Returns all the entities that satisfy the given query. - -```js -const users = db.user.findMany({ - where: { - followersCount: { - gte: 1000, - }, +```ts +users.updateMany((q) => q.where({ name: (name) => name.startsWith('J') }), { + data(user) { + user.name = user.name.toUpperCase() + }, + orderBy: { + name: 'asc', + id: 'desc', }, }) ``` -### `count` - -Returns the number of records for the given model. +You can sort by an ordered list of criteria by passing an array to `orderBy`. Each entry is applied in sequence: the first entry determines the primary sort, and each subsequent entry breaks ties among records that compare equal under the preceding criteria. -```js -db.user.create() -db.user.create() - -db.user.count() // 2 +```ts +users.findMany(undefined, { + orderBy: [{ age: 'asc' }, { name: 'desc' }] +}) ``` -Can accept an optional query argument to filter the records before counting them. +## Relations -```js -db.user.count({ - where: { - role: { - equals: 'reader', - }, - }, -}) -``` +You can define relations by calling the `.defineRelations()` method on the collection. -### `getAll` +- [One-to-one](#one-to-one) +- [One-to-many](#one-to-many) +- [One-to-many (inversed)](#one-to-many-inversed) +- [Many-to-many](#many-to-many) +- [Through relations](#through-relations) +- [Unique relations](#unique-relations) +- [Ambiguous relations](#ambiguous-relations) +- [Polymorphic relations](#polymorphic-relations) -Returns all the entities of the given model. +### Defining relations -```js -const allUsers = db.user.getAll() -``` +Below, you can find examples of defining various types of relations, but there are a few things that apply to all of them: -### `update` +- Describe relations on the schema level using your schema library. The `.defineRelations()` API has no effect on the model's schema/types and only operates on known properties; +- Relations are described _after_ a collection is defined (to prevent circular references); +- Relations do not require explicit `foreignKey` associations and instead are bound to internal IDs of related records. -Updates the first entity that matches the query. +### One-to-one -```js -const updatedUser = db.user.update({ - // Query for the entity to modify. - where: { - id: { - equals: 'abc-123', - }, - }, - // Provide partial next data to be - // merged with the existing properties. - data: { - // Specify the exact next value. - firstName: 'John', - - // Alternatively, derive the next value from - // the previous one and the unmodified entity. - role: (prevRole, user) => reformatRole(prevRole), +```ts +const userSchema = z.object({ + // In Zod, relational properties are best described as getters + // so they can produce self-referencing schemas. + get country() { + return countrySchema }, }) -``` +const countrySchema = z.object({ code: z.string() }) -### `updateMany` +const users = new Collection({ schema: userSchema }) +const countries = new Collection({ schema: countrySchema }) -Updates multiple entities that match the query. +// Declare the relations on the `users` collection. +users.defineRelations(({ one }) => ({ + // `user.country` is a one-of relation to the `countries` collection. + country: one(countries), +})) -```js -const updatedUsers = db.user.updateMany({ - // Query for the entity to modify. - where: { - id: { - in: ['abc-123', 'def-456'], - }, - }, - // Provide partial next data to be - // merged with the existing properties. - data: { - firstName: (firstName) => firstName.toUpperCase(), - }, +const user = await users.create({ + country: await countries.create({ code: 'usa' }), }) +user.country // { code: 'usa' } ``` -### `delete` - -Deletes the entity that satisfies the given query. +### One-to-many -```js -const deletedUser = db.user.delete({ - where: { - followersCount: { - equals: 0, - }, +```ts +const postSchema = z.object({ + get comments() { + return z.array(countrySchema) }, }) -``` +const commentSchema = z.object({ + text: z.string(), +}) -### `deleteMany` +const posts = new Collection({ schema: postSchema }) +const comments = new Collection({ schema: commentSchema }) -Deletes multiple entities that match the query. +posts.defineRelations(({ many }) => ({ + comments: many(comments), +})) -```js -const deletedUsers = db.user.deleteMany({ - where: { - followersCount: { - lt: 10, - }, - }, +await posts.create({ + comments: [ + await comments.create({ text: 'First!' }), + await comments.create({ text: 'Thanks for watching.' }), + ], }) ``` -### `toHandlers` - -Generates request handlers for the given model to use with [Mock Service Worker](https://github.com/mswjs/msw). All generated handlers are automatically connected to the respective [model methods](#model-methods), enabling you to perform CRUD operations against your mocked database. - -#### REST handlers +### One-to-many (inversed) -```js -import { factory, primaryKey } from '@mswjs/data' +Two collections may self-reference each other. For example, `post.comments` is a list of comments while each `comment.post` references to the parent post. -const db = factory({ - user: { - id: primaryKey(String), - firstName: String, +```ts +const postSchema = z.object({ + get comments() { + return z.array(countrySchema) }, }) - -// Generates REST API request handlers. -db.user.toHandlers('rest') -``` - -- Learn more about [REST API mocking integration](#generate-rest-api). - -#### GraphQL handlers - -```js -import { factory, primaryKey } from '@mswjs/data' - -const db = factory({ - user: { - id: primaryKey(String), - firstName: String, +const commentSchema = z.object({ + text: z.string(), + get post() { + return postSchema }, }) -// Generates GraphQL API request handlers. -db.user.toHandlers('graphql') -``` - -- Learn more about [GraphQL API mocking integration](#generate-graphql-api). +const posts = new Collection({ schema: postSchema }) +const comments = new Collection({ schema: commentSchema }) -#### Scoping handlers +posts.defineRelations(({ many }) => ({ + comments: many(comments), +})) +comments.defineRelations(({ one }) => ({ + post: one(posts), +})) -The `.toHandlers()` method supports an optional second `baseUrl` argument to scope the generated handlers to a given endpoint: +await posts.create({ + comments: [await comments.create({ text: 'First!' })], +}) -```js -db.user.toHandlers('rest', 'https://example.com') -db.user.toHandlers('graphql', 'https://example.com/graphql') +const comment = comments.findFirst((q) => q.where({ text: 'First!' })) +comment.post // { comments: [{ text: 'First', post: Circular }] } ``` -## Recipes - -- **Modeling:** - - [Nullable properties](#nullable-properties) - - [Nested structures](#nested-structures) - - [Model relationships](#model-relationships) -- **Querying:** - - [Querying data](#querying-data) - - [Strict mode](#strict-mode) - - [Pagination](#pagination) - - [Sorting](#sorting) - -### Nullable properties +> Inversed relations are updated automatically. Whenever you add a new comment to a post, both `post.comments` and `comment.post` are updated to reference each other. The same is true when setting a new parent `post` on the comment. -By default, all model properties are non-nullable. You can use the `nullable` function to mark a property as nullable: +### Many-to-many -```js -import { factory, primaryKey, nullable } from '@mswjs/data' +In the next example, every `user` may have multiple `posts` while each `post` may have multiple `authors`. -const db = factory({ - user: { - id: primaryKey(String), - firstName: String, - // "user.age" is a nullable property. - age: nullable(Number), +```ts +const userSchema = z.object({ + get posts() { + return z.array(postSchemas) }, }) - -db.user.create({ - id: 'user-1', - firstName: 'John', - // Nullable properties can be explicit null as the initial value. - age: null, -}) - -db.user.update({ - where: { - id: { - equals: 'user-1', - }, - }, - data: { - // Nullable properties can be updated to null. - age: null, +const postSchema = z.object({ + get authors() { + return z.array(userSchema) }, }) -``` - -> You can define [Nullable relationships](#nullable-relationships) in the same manner. - -When using Typescript, you can manually set the type of the property when it cannot be otherwise inferred from the seeding function, such as when you want a property to default to `null`: -```typescript -import { factory, primaryKey, nullable } from '@mswjs/data' +const users = new Collection({ schema: userSchema }) +const posts = new Collection({ schema: postSchema }) -const db = factory({ - user: { - id: primaryKey(String), - age: nullable(() => null), - }, -}) +users.defineRelations(({ many }) => ({ + posts: many(posts), +})) +posts.defineRelations(({ many }) => ({ + authors: many(users), +})) ``` -### Nested structures +### Through relations -You may use nested objects to design a complex structure of your model: +Since relational properties resolve via getters, there's no need to define special "through" relations to reference one model through a relation from another. -```js -import { factory, primaryKey, nullable } from '@mswjs/data' - -const db = factory({ - user: { - id: primaryKey(String), - address: { - billing: { - street: String, - city: nullable(String), - }, - }, - }, -}) +```ts +const owners = new Collection({ schema: ownerSchema }) +const cars = new Collection({ schema: carSchema }) +const mechanics = new Collection({ schema: mechanicSchema }) -// You can then create and query your data -// based on the nested properties. +owners.defineRelations(({ many }) => ({ + cars: many(cars), +})) +cars.defineRelations(({ one }) => ({ + owner: one(owners), +})) +mechanics.defineRelations(({ one }) => ({ + car: one(cars), +})) -db.user.create({ - id: 'user-1', - address: { - billing: { - street: 'Baker st.', - city: 'London', - }, - }, -}) +const owner = await owners.create({ name: 'John' }) +const car = await cars.create({ brand: 'bmw', owner }) +const mechanic = await mechanics.create({ name: 'Kyle', car }) -db.user.update({ - where: { - id: { - equals: 'user-1', - }, - }, - data: { - address: { - billing: { - street: 'Sunwell ave.', - city: null, - }, - }, - }, -}) +mechanic.car.owner.name // "John" ``` -> Note that you **cannot** mark a nested property as the [primary key](#using-the-primary-key). +> Note that although `mechanics` does not define an explicit relation to `owners`, you can get the owner of the car associated with a mechanic through the `car` relation. -You may also specify _relationships_ nested deeply in your model: +### Unique relations -```js -factory({ - user: { - id: primaryKey(String), - address: { - billing: { - country: oneOf('country'), - }, - }, - }, - country: { - code: primaryKey(String), - }, -}) -``` +You can mark a relation as unique by setting the `unique` property of the relation options to `true`. Unique relations cannot reference foreign records that are already associated with other owner records. -> Learn more about [Model relationships](#model-relationships). - -### Model relationships +```ts +posts.defineRelations(({ one }) => ({ + author: one(users, { unique: true }), +})) +``` -- [One-to-One](#one-to-one) -- [One-to-Many](#one-to-many) -- [Many-to-One](#many-to-one) -- [Unique relationships](#unique-relationships) -- [Nullable relationships](#nullable-relationships) +> In this example, the `author` of each post points to a single _unique_ user. If a post attempts to set its author to a user that's already associated with another post, an error will be thrown. -Relationship is a way for a model to reference another model. +### Ambiguous relations -#### One-to-One +You can use the `role` option of the relation to disambiguate between multiple properties referencing the same foreign model. -```js -import { factory, primaryKey, oneOf } from '@mswjs/data' +For example, a single `post` may have both `author` and `reviewer` referencing the same `user` model. To make those properties pointing to _different_ user records, use the `role` that acts as a relation identifier. This way, the library will update the corresponding relational properties for both `users` and `posts` when the referenced relation is updated. -const db = factory({ - user: { - id: primaryKey(String), - firstName: String, - }, - post: { - id: primaryKey(String), - title: String, - // The "post.author" references a "user" model. - author: oneOf('user'), - }, -}) +```ts +const users = new Collection({ schema: userSchema }) +const posts = new Collection({ schema: postSchema }) -const user = db.user.create({ firstName: 'John' }) -const post = db.post.create({ - title: 'My journey', - // Use a "user" entity as the actual value of this post's author. - author: user, -}) +users.defineRelations(({ many }) => ({ + posts: many(posts, { role: 'author' }), + reviews: many(posts, { role: 'reviewer' }), +})) -post.author.firstName // "John" +posts.defineRelations(({ one }) => ({ + author: one(user, { role: 'author' }), + reviewer: one(user, { role: 'reviewer' }), +})) ``` -#### One-to-Many - -```js -import { factory, primaryKey, manyOf } from '@mswjs/data' +> The `role` property acts as a de-facto ID of a relation when synchronizing related models. -const db = factory({ - user: { - id: primaryKey(String), - // "user.posts" is a list of the "post" entities. - posts: manyOf('post'), - }, - post: { - id: primaryKey(String), - title: String, - }, -}) +### Polymorphic relations -const posts = [ - db.post.create({ title: 'First' }), - db.post.create({ title: 'Second' }), -] +Provide an array of foreign collections to a relation to define it as _polymorphic_. -const user = db.user.create({ - // Assign the list of existing posts to this user. - posts, -}) +```ts +const posts = new Collection({ schema: postSchema }) +const images = new Collection({ schema: imageSchema }) +const videos = new Collection({ schema: videoSchema }) -user.posts // [{ title: "First" }, { title: "Second" }] +posts.defineRelations(({ many }) => ({ + // Providing a record of foreign collections allows + // all of their records to be set as the value. + attachments: many([images, videos]), +})) +images.defineRelations(({ one }) => ({ + post: one(posts), +})) +videos.defineRelations(({ one }) => ({ + post: one(posts), +})) ``` -#### Many-to-One +> In this example, `post.attachments` is an array of either `images` or `videos`, where records from both collections are allowed. -```js -import { factory, primaryKey, oneOf } from '@mswjs/data' +## Error handling -const db = factory({ - country: { - name: primaryKey(String), - }, - user: { - id: primaryKey(String), - country: oneOf('country'), - }, - car: { - serialNumber: primaryKey(String), - country: oneOf('country'), - }, -}) +Data provides multiple different error classes to help you differentiate and handle different errors. -const usa = db.country.create({ name: 'The United States of America' }) +### `OperationError` -// Create a "user" and a "car" with the same country. -db.user.create({ country: usa }) -db.car.create({ country: usa }) -``` +- `code` ``, the error code describing the failed operation; +- `cause` `` (_optional_), a reference to the original thrown error. -#### Unique relationships +Thrown whenever performing a record operation fails. For example: -Both `oneOf` and `manyOf` relationships may be marked as unique. A unique relationship is where a referenced entity cannot be assigned to another entity more than once. +- When creating a new record whose initial values do not match the collection's schema; +- When there are no records found for a strict query. -In the example below we define the "user" and "invitation" models, and design their relationship so that one invitation cannot be assigned to multiple users. +### `RelationError` -```js -import { factory, primaryKey, oneOf } from '@mswjs/data' +- `code` ``, the error code describing the relation operation; +- `info` ``, additional error information; + - `path` ``, path of the relational property; + - `ownerCollection` ``, a reference to the owner collection; + - `foreignCollection` `>`, an array of foreign collections referenced by this relation; + - `options` `RelationDefinitionOptions`, the options object passed upon decaring this relation. -const db = factory({ - user: { - id: primaryKey(String), - invitation: oneOf('invitation', { unique: true }), - }, - invitation: { - id: primaryKey(String), - }, -}) +Thrown whenever performing a relation operation fails. For example: -const invitation = db.invitation.create() +- When attempting to reference a foreign record that's already associated with another record in a unique relation; +- When directly assigning value to a relational property. -const john = db.user.create({ invitation }) +--- -// Assigning the invitation already used by "john" -// will throw an exception when creating this entity. -const karl = db.user.create({ invitation }) -``` +## API -#### Nullable relationships +### `new Collection(options)` -Both `oneOf` and `manyOf` relationships may be passed to `nullable` to allow -instantiating and updating that relation to null. +- `options` `` + - `schema` [Standard Schema][standard-schema] A schema describing each record in this collection. + - `extensions` (optional) An array of [extensions](#extensions) to use on this collection. -```js -import { factory, primaryKey, oneOf, nullable } from '@mswjs/data' +Creates a new collection of data. -const db = factory({ - user: { - id: primaryKey(String), - invitation: nullable(oneOf('invitation')), - friends: nullable(manyOf('user')), - }, - invitation: { - id: primaryKey(String), - }, -}) +#### `.create(initialValues)` -const invitation = db.invitation.create() +- `initialValues` Initial values for the new record. -// Nullable relationships are instantiated with null. -const john = db.user.create({ invitation }) // john.friends === null -const kate = db.user.create({ friends: [john] }) // kate.invitation === null +Creates a single record with the provided initial values. -db.user.updateMany({ - where: { - id: { - in: [john.id, kate.id], - }, - }, - data: { - // Nullable relationships can be updated to null. - invitation: null, - friends: null, - }, -}) +```ts +const user = await users.create({ id: 1, name: 'John' }) ``` -### Querying data - -This library supports querying of the seeded data similar to how one would query a SQL database. The data is queried based on its properties. A query you construct depends on the value type you are querying. +> The `.create()` method returns a promise to support potential asynchronous transformations in your schema. -#### String operators +#### `.createMany(count, initialValuesFactory)` -- `equals` -- `notEquals` -- `contains` -- `notContains` -- `in` -- `notIn` +- `count` `` A number of records to create. +- `initialValuesFactory` `` A function that returns initial values for each record. -#### Number operators +Creates multiple records with the initial value factory. -- `equals` -- `notEquals` -- `gt` -- `gte` -- `lt` -- `lte` -- `between` -- `notBetween` -- `in` -- `notIn` - -#### Boolean operators +```ts +const users = await users.createMany(5, (index) => ({ + id: index + 1, + name: 'John', +})) +``` -- `equals` -- `notEquals` +The initial value factory function accepts the `index` argument indicating the index of the record that's being created. Use it, as well as the function's closure, to generate unique or random values. -#### Date operators +#### `.findFirst(query)` -- `equals` -- `notEquals` -- `gt` -- `gte` -- `lt` -- `lte` +- `query` [`Query`](#new-querypredicate) A query matching the record. -#### Query example +Returns the first record matching the query. -```js -const db = factory({ - post: { - id: String, - likes: Number, - isDraft: Boolean, - }, +```ts +const users = new Collection({ + schema: z.object({ + id: z.number(), + name: z.string(), + }), }) -// Returns the list of `post` entities -// that satisfy the given query. -const popularPosts = db.post.findMany({ - where: { - likes: { - gte: 1000, - }, - isDraft: { - equals: false, - }, - }, -}) +await users.create({ id: 1, name: 'John' }) +await users.create({ id: 2, name: 'John' }) + +users.findFirst((q) => q.where({ name: 'John' })) +// { id: 1, name: 'John' } ``` -### Strict mode +#### `.findMany(query)` -When querying or updating the entities you can supply the `strict: boolean` property on the query. When supplied, if a query operation fails (i.e. no entity found), the library will throw an exception. +- `query` [`Query`](#new-querypredicate) A query matching the records. -```js -import { factory, primaryKey } from '@mswjs/data' +Returns all records matching the query. -const db = factory({ - user: { - id: primaryKey(String), - }, +```ts +const users = new Collection({ + schema: z.object({ + id: z.number(), + name: z.string(), + }), }) -db.user.create({ id: 'abc-123' }) +await users.create({ id: 1, name: 'John' }) +await users.create({ id: 2, name: 'John' }) -// This will throw an exception, because there are -// no "user" entities matching this query. -db.user.findFirst({ - where: { - id: { - equals: 'def-456', - }, - }, - strict: true, -}) +users.findMany((q) => q.where({ name: 'John' })) +// [{ id: 1, name: 'John' }, { id: 2, name: 'John' }] ``` -### Pagination +#### `.update(query, options)` -This library supports _offset-based_ and _cursor-based_ pagination of the `findMany` method results. +- `query` [`Query`](#new-querypredicate) A query matching the record. +- `options` `` + - `data` A function that produces changes by modifying the previous record. -#### Offset-based pagination +Updates the first record matching the query. Returns a promise that resolves with the updated record. -```js -const db = factory({ - post: { - id: primaryKey(String), - category: String, - }, -}) - -db.post.findMany({ - where: { - category: { - equals: 'Science', - }, +```ts +// Change the name for the user with a specific `id`. +const updatedUser = await users.update((q) => q.where({ id: 123 }), { + data(user) { + user.name = 'Johnatan' }, - take: 15, - skip: 10, }) ``` -#### Cursor-based pagination +> Update methods return a promise in order to support potential asynchronous transformations defined in your schema. -The `cursor` option of the `findMany` query expects a primary key value of a model to start the pagination from. +The `data` function allows you to perform multiple updates upon a record by mutating that record directly. Think of it as a draft function from libraries like `immer` or `mutative` because that's precisely what it is! -```js -const db = factory({ - post: { - // The `id` primary key will be used as a cursor. - id: primaryKey(String), - category: String, - }, -}) +You can also provide a record reference as the predicate to the `.update()` method to update that particular record: -const firstPage = db.post.findMany({ - where: { - category: { - equals: 'Science', - }, - }, - take: 15, - cursor: null, -}) - -const secondPage = db.post.findMany({ - where: { - category: { - equals: 'Science', - }, +```ts +const user = users.findFirst((q) => q.where({ id: 123 })) +await users.update(user, { + // 👆👆 + data(user) { + user.id = 456 }, - take: 15, - // The second page will start from the last post - // of the `firstPage`. - cursor: firstPage[firstPage.length - 1].id, }) ``` -### Sorting +#### `.updateMany(query, options)` -#### Basic sorting +- `query` [`Query`](#new-querypredicate) A query matching the records. +- `options` `` + - `data` Changes to apply to each record. -```js -const db = factory({ - post: { - id: primaryKey(String), - title: String, - }, -}) +Updates all records matching the query. Returns a promise that resolves with an array containing the updated records. -// Return first 10 posts in the "Science" category -// sorted by the post's "title". -db.post.findMany({ - where: { - category: { - equals: 'Science', - }, - }, - take: 10, - orderBy: { - title: 'asc', +```ts +// Find all the users with the name "John" +// and make their name truly stand out! +const updatedUsers = await users.updateMany((q) => q.where({ name: 'John' }), { + data(user) { + user.name = user.name.toUpperCase() }, }) ``` -> You can use `orderBy` with [pagination](#pagination). +#### `.delete(query)` -#### Sorting by relational properties +- `query` [`Query`](#new-querypredicate) A query matching the record. -```js -const db = factory({ - post: { - id: primaryKey(String), - title: String, - author: oneOf('user'), - }, - user: { - id: primaryKey(String), - firstName: String, - }, -}) +Deletes the first record matching the query. Returns the deleted record. -// Return all posts in the "Science" category -// sorted by the post author's first name. -db.post.findMany({ - where: { - category: { - equals: 'Science', - }, - }, - orderBy: { - author: { - firstName: 'asc', - }, - }, -}) +```ts +// Delete a user with a particular `id`. +const deletedUser = users.delete((q) => q.where({ id: 123 })) ``` -#### Sorting by multiple criteria - -Provide a list of criteria to sort the query result against. +You can also provide a record reference as the predicate to the `.delete()` method to delete that particular record: -```js -db.post.findMany({ - orderBy: [ - { - title: 'asc', - }, - { - views: 'desc', - }, - ], -}) +```ts +const user = users.findFirst((q) => q.where({ id: 123 })) +users.delete(user) ``` -You can also use a combination of direct and relational properties on a single query: +#### `.deleteMany(query)` -```js -db.post.findMany({ - orderBy: [ - { - title: 'asc', - }, - { - author: { - firstName: 'asc', - }, - }, - ], -}) -``` +- `query` [`Query`](#new-querypredicate) A query matching the records. -### Database utilities +Deletes all records matching the query. Returns an array containing the deleted records. -#### `drop` +```ts +// Delete all users whose trial period has expired. +const deletedUsers = users.deleteMany((q) => + q.where({ trial: { expiresAt: (expiresAt) => expiresAt <= Date.now() } }), +) +``` -Drops the given database, deleting all its entities. +#### `.defineRelations(definition)` -```js -import { factory, drop } from '@mswjs/data' +- `definition` `` A function that accepts relation utilities and returns an object with relational properties. -const db = factory({...}) +Defines relations on the current collection. -drop(db) -``` +```ts +const users = new Collection({ schema: userSchema }) +const posts = new Collection({ schema: postSchema }) -### Usage with `Faker` +users.defineRelations(({ many }) => ({ + // `user.posts` is a many-of relation to `posts`. + posts: many(posts) +})) +``` -Libraries like [Faker](https://github.com/faker-js/faker) can help you generate fake data for your models. +> You can define nested relational properties by nesting them in the object returned from `.defineRelations()`. -```js -import { seed, datatype, name } from '@faker-js/faker' -import { factory, primaryKey } from '@mswjs/data' +##### Relational utilities -// (Optional) Seed `faker` to ensure reproducible -// random values of model properties. -seed(123) +The following relational utilities are exposed in the argument to this method: -factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: name.firstName, - }, -}) -``` +- `one(collection[, options])`, defines a one-of relation to the given collection; +- `many(collection[, options])`, defines a many-of relation to the given collection. -### Collocated updates +##### Relation options -When you wish to update a parent entity and one of its relational properties at the same time, collocate such an update operation via the updater function of the [`update`](#update) method. +- `unique` ``, marks this relation as unique. Foreign records referenced by this relation cannot be referenced by other models. -```js -import { factory, primaryKey, oneOf } from '@mswjs/data' +```ts +users.defineRelations(({ many }) => ({ + posts: many(posts) +})) +posts.defineRelations(({ one }) => ({ + author: one(users, { unique: true }) +})) -const db = factory({ - post: { - id: primaryKey(String), - title: String, - revision: oneOf('revision'), - }, - revision: { - id: primaryKey(String), - updatedAt: () => new Date(), - }, +const john = await users.create({ + name: 'John', + // `john` is associated as the `author` of this post now. + posts: [await posts.create({ title: 'First post' })] }) -db.post.update({ - where: { - id: { equals: 'post-1' }, - }, - data: { - title: 'Renamed post', - // The next value of the "post.revision" - // is returned from this updater function. - revision(prevRevision, post) { - // Update this post's revision as you'd do usually, - // but nested within the post's update operation. - return db.revision.update({ - where: { - id: { equals: post.revision.id }, - }, - data: { - updatedAt: Date.now(), - }, - }) - }, - }, +await users.create({ + name: 'Katy', + // Creating this user will error because it tries to list + // a post which `author` already references to another user. + posts: [john.posts[0]] }) ``` -> While the `post` above will get updated, both `post.revision` and the respective `revision` standalone will be updated as well. +- `role` ``, an identifier to differentiate ambiguous relations to the same foreign collection; -Collocating nested updates grants you a predictable behavior when changing multiple related entities. +```ts +users.defineRelations(({ many }) => ({ + // Both `users` and `posts` reference each other in multiple keys. + // Using `role` helps the library understand which keys are connected. + posts: many(posts, { role: 'author' }), + underReview: many(posts, { role: 'reviewer' }) +})) -## Usage with API mocks +posts.defineRelations(({ one, many }) => ({ + author: one(users, { role: 'author' }), + reviewers: many(users, { role: 'reviewer' }) +})) +``` -While this library can be used standalone, it brings a tremendous benefit in a combination with tools like [Mock Service Worker](https://github.com/mswjs). We provide a build-in API to quickly generate API request handlers based on your models, representing model interactions via HTTP requests. +- `onDelete` `"cascade" | undefined`, decides how to handle referenced foreign records when the owner is deleted. -### Generate request handlers +```ts +users.defineRelations(({ many }) => ({ + // If a user gets deleted, delete all of the `posts` associated with them. + posts: many(posts, { onDelete: 'cascade' }) +})) +posts.defineRelations(({ one }) => ({ + author: one(users) +})) +``` -Both REST and GraphQL [request handlers]() can be generated from a model using the [`.toHandlers()`](#toHandlers) method of that model. When generated, request handlers automatically have that model's CRUD methods like `POST /user` or `mutation CreateUser`. +### `new Query([predicate])` -#### Generate REST API +- `predicate` (optional) An object or a function that acts as a predicate for records. -REST API request handlers can be generated by calling the `.toHandlers('rest')` method on the respective factory model. +Creates a new query to match records in a collection. Normally, you query records through the querying methods of the collection (see [Querying](#querying)). You can, however, construct a type-safe `Query` class to abstract common queries or query builders. ```ts -import { setupServer } from 'msw/node' -import { factory, primaryKey } from '@mswjs/data' - -const db = factory({ - user: { - id: primaryKey(String), - firstName: String, - }, +const userSchema = z.object({ + id: z.number(), + subscribed: z.boolean().default(false), + role: z.enum(['user', 'editor', 'admin']).default('user'), }) -const handlers = [...db.user.toHandlers('rest')] - -// Establish requests interception. -const server = setupServer(...handlers) -server.listen() +// Creates a query builder for the users schema. +const query = new Query() ``` -Given the "user" model definition above, the following request handlers are generated and connected to the respective database operations: +#### `.where(predicate)` -- `GET /users/:id` (where "id" is your model's primary key), returns a user by ID; -- `GET /users`, returns all users (supports [pagination](#pagination)); -- `POST /users`, creates a new user; -- `PUT /users/:id`, updates an existing user by ID; -- `DELETE /users/:id`, deletes an existing user by ID; +- `predicate` A predicate for the records. +- Returns: [`Query`](#new-querypredicate). -The "/user" part of the route is derived from your model name. For example, if you had a "post" model defined in your `factory`, then the generated handlers would be `/posts`, `/posts/:id`, etc. +```ts +query.where({ id: 2 }) +``` -With the request handlers generated and MSW configured, you can query the "database" using REST API: +#### `.or(predicate)` -```js -// Create a new user in the database. -fetch('/users', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - id: 'abc-123', - firstName: 'John', - }), -}) +- `predicate` A predicate or another [`Query`](#new-querypredicate). +- Returns: [`Query`](#new-querypredicate). + +Creates a new query, merging the previous predicates with the new one under a `OR` relation. A record may match _any predicate_ to be considered matching. + +```ts +const unsubscribedOrEditorsQuery = query.or( + query.where({ subscribed: false }), + query.where({ role: 'editor' }), +) ``` -#### Generate GraphQL API +#### `.and(predicate)` -GraphQL API request handlers can be generated by calling the `.toHandlers('graphql')` method on the respective factory model. +- `predicate` A predicate or another [`Query`](#new-querypredicate). +- Returns: [`Query`](#new-querypredicate). -```js -import { setupServer } from 'msw/node' -import { factory, primaryKey } from '@mswjs/data' +Creates a new query, merging the previous predicates with the new one under a `AND` relation. A record must match _all predicates_ to be considered matching. -const db = factory({ - user: { - id: primaryKey(String), - firstName: String, - }, -}) +```ts +const exactAdminQuery = query.and( + query.where({ id: 1 }), + query.where({ role: 'admin' }), +) +``` -const handlers = [...db.user.toHandlers('graphql')] +#### `.test(record)` -// Establish requests interception. -const server = setupServer(...handlers) -server.listen() +- `record` `` A reference to a record to test. +- Returns `` Indicates whether the given record matches the query. + +```ts +query.test({ id: 1 }) ``` -Given the "user" model definition above, the following request handlers are generated and connected to the respective database operations: +--- -- `user(where: UserQueryInput): User`, returns a user matching the query; -- `users(where: UserQueryInput, cursor: ID, skip: Int, take: Int): [User!]`, returns all users matching the query (supports [pagination](#pagination)); -- `createUser(data: UserInput!): User!`, creates a new user; -- `updateUser(where: UserQueryInput!, data: UserInput!): User!`, updates a user that match the `where` query; -- `updateUsers(where: UserQueryInput!, data: UserInput!): [User!]`, updates multiple users that match the `where` query; -- `deleteUser(where: UserQueryInput!): User!`, deletes a user that match the `where` query; -- `deleteUsers(where: UserQueryInput!): [User!]`, deletes multiple users that match the `where` query. +## Extensions -The "User" part of the GraphQL operation names is derived from your model's name. For example, if you had a "post" model defined in your `factory`, then the generated handlers would have operations like `post`, `createPost`, `updatePosts`, etc. +You can extend the behavior of collections via _extensions_. The library comes with the following default extensions, but you can always [create your own](#custom-extensions). -With the request handlers generated and MSW configured, you can query the database using GraphQL API: +### Default extensions -```js -import { gql, useQuery } from '@apollo/client' +#### `sync()` -const CREATE_USER = gql` - query CreateUser($initialValues: UserInput!) { - createUser(data: $initialValues) { - firstName - } - } -` +> [!WARNING] +> The `sync()` extension is browser-only. It will be ignored in Node.js. -useQuery(CREATE_USER, { - variables: { - initialValues: { - firstName: 'John', - }, - }, +Synchronizes collection changes, like creating/updating/deleting records, with the same collection in another browser tab via a `BroadcastChannel`. + +```ts +import { Collection } from '@msw/data' +import { sync } from '@msw/data/extensions/sync' + +const users = new Collection({ + schema, + extensions: [sync()], }) ``` -### Manual integration +#### `persist()` -To gain more control over the mocks and implement more complex mocking scenarios (like authentication), consider manual integration of this library with your API mocking solution. +> [!WARNING] +> The `persist()` extension is browser-only. It will be ignored in Node.js. -Take a look at how you can create an entity based on the user's authentication status in a test: +Persist the records in the collection between page reloads. -```js -import { rest } from 'msw' -import { setupServer } from 'msw/node' -import { factory, primaryKey } from '@mswjs/data' +```ts +import { Collection } from '@msw/data' +import { persist } from '@msw/data/extensions/persist' -const db = factory({ - post: { - id: primaryKey(String), - title: String, - }, +const users = new Collection({ + schema, + extensions: [persist()], }) +``` -const handlers = [ - rest.post('/post', (req, res, cxt) => { - // Only authenticated users can create new posts. - if (req.headers.get('authorization') === 'Bearer AUTH_TOKEN') { - return res(ctx.status(403)) - } - - // Create a new entity for the "post" model. - const newPost = db.post.create(req.body) - - // Respond with a mocked response. - return res(ctx.status(201), ctx.json({ post: newPost })) - }), -] +### Custom extensions -// Establish requests interception. -const server = setupServer(...handlers) -server.listen() +```ts +// my-extension.ts +import { defineExtension } from '@msw/data/extensions' + +export function myExtension() { + return defineExtension({ + name: 'my-extension', + extend(collection) { + // Your logic here. + }, + }) +} ``` -## Honorable mentions +```ts +import { Collection } from '@msw/data' +import { myExtension } from './my-extension.js' -- [Prisma](https://www.prisma.io) for inspiring the querying client. -- [Lenz Weber](https://twitter.com/phry) and [Matt Sutkowski](https://twitter.com/de_stroy) for great help with the TypeScript support. +new Collection({ schema, extensions: [myExtension()] }) +``` diff --git a/commitlint.config.js b/commitlint.config.js deleted file mode 100644 index 98ee7dfc..00000000 --- a/commitlint.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - extends: ['@commitlint/config-conventional'], -} diff --git a/decisions/primary-key.md b/decisions/primary-key.md new file mode 100644 index 00000000..4cebc418 --- /dev/null +++ b/decisions/primary-key.md @@ -0,0 +1,3 @@ +# Primary key + +The `primaryKey()` function has been abolished in favor of internal record ID. Every record gets assigned a random immutable UUID upon its creation. The library uses those internal IDs primarily for associating records with each other in relations. The user is free to treat any of the defined model properties as a "primary key", but no such functionality longer exists in the library. diff --git a/decisions/query-syntax.md b/decisions/query-syntax.md new file mode 100644 index 00000000..459109a6 --- /dev/null +++ b/decisions/query-syntax.md @@ -0,0 +1,52 @@ +# Query syntax + +## Query methods + +Query methods like `.findFirst()`, `.findMany()`, `.update()`, `.updateMany()`, and others were chosen due to developer familiarity as they might find those (or similar) methods in ORM libraries, like Prisma or Drizzle. I personally like create-find-update-delete terminology. + +## Query builder + +I went with the query builder pattern over something like `{ OR, AND }` key syntax you might find in Prisma because I dislike mixing record properties and logical properties. + +```ts +users.findFirst({ + where: { + OR: [{ id: 2 }, { name: 'Bob', AND: [{ subscribed: true }] }], + }, +}) +``` + +Mixing record properties and logical properties results in queries that are hard to read. Compare this to the builder syntax that wraps predicates in logical expressions instead: + +```ts +users.findFirst((q) => + q.where({ id: 2 }).or(q.where({ name: 'Bob', subscribed: true })), +) +``` + +Query predicates always include only the record properties, which makes them easy to write. They can be abstracted and composed in any arrangement of logical `q.or` or `q.and` sequences. This composition also allows for mix-and-matching of syntax with zero additional handling on the library's side: + +```ts +users.findFirst((q) => q.or(q.where({ id: 2 }), q.where({ name: 'Bob' }))) +``` + +## Convenience keys + +Convenience keys like `equals`, `in`, `lg`, `notContains`, and others were dropped in favor of function predicates. Functions are infinitely more powerful and the internal overhead of differentiating between reserved convenience keys and user-provided keys is not worth whatever little brevity gained as a result. + +You can describe custom logic in your query predicates since any field (or the entire record) can be described as a function. + +```ts +users.findFirst((q) => + q.where({ + id: (id) => isList.includes(id), // vs { in: idList } + }), +) + +users.findFirst((q) => + q.where((user) => { + // Custom predicate accepting the entire record. + return hasRole('admin', user) + }), +) +``` diff --git a/decisions/standard-schema.md b/decisions/standard-schema.md new file mode 100644 index 00000000..c879cc3e --- /dev/null +++ b/decisions/standard-schema.md @@ -0,0 +1,39 @@ +# Standard Schema + +I chose to adopt [Standard Schema](https://standardschema.dev/) as the way to define models. This library was never meant to have a custom modeling syntax you have to learn. With the introduction of Standard Schema, it can now rely on any standard-compliant modeling library as the input, making it easier for developers to adopt. + +```ts +import { Collection } from '@msw/data' +import { z } from 'zod' + +const users = new Collection({ + schema: z.object({ + id: z.number(), + name: z.string().optional(), + }), +}) +``` + +> Above, Zod is used to model the `users` collection. You can use any of the Standard Schema-compatible libraries instead. + +This decision also allows the library to offload custom features, like derived properties, to the schema libraries. For example, to derive a model's property from another property, you follow your schema library's best practices. + +```ts +new Collection({ + schema: z + .object({ + name: z.string(), + email: z.email(), + }) + .transform((user) => { + user.email = `${user.name.toLowerCase()}@email.com` + return user + }), +}) +``` + +> Above, I'm using Zod's `.transform()` method to derive the `email` field from the `name` field. + +## Model restrictions + +In order to support internal IDs and relations, user-defined models _must be either objects or arrays_. diff --git a/extensions/package.json b/extensions/package.json new file mode 100644 index 00000000..b855b3a4 --- /dev/null +++ b/extensions/package.json @@ -0,0 +1,15 @@ +{ + "type": "module", + "main": "./../build/extensions/index.js", + "types": "./../build/extensions/index.d.ts", + "exports": { + "./sync": { + "types": "./../build/extensions/sync.d.ts", + "default": "./../build/extensions/sync.js" + }, + "./persist": { + "types": "./../build/extensions/persist.d.ts", + "default": "./../build/extensions/persist.js" + } + } +} diff --git a/logo.svg b/logo.svg index f66e60e3..30f32b6d 100644 --- a/logo.svg +++ b/logo.svg @@ -26,4 +26,4 @@ - \ No newline at end of file + diff --git a/ossjs.release.config.js b/ossjs.release.config.js deleted file mode 100644 index 3f582643..00000000 --- a/ossjs.release.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - script: 'yarn publish --new-version $RELEASE_VERSION', -} diff --git a/package.json b/package.json index 71d90cfb..8968c5ef 100644 --- a/package.json +++ b/package.json @@ -1,69 +1,63 @@ { - "name": "@mswjs/data", - "description": "Data modeling and relation library for testing JavaScript applications.", - "version": "0.10.1", - "main": "lib/index.js", - "typings": "lib/index.d.ts", - "author": "Artem Zakharchenko", - "license": "MIT", + "type": "module", + "name": "@msw/data", + "version": "1.1.7", + "description": "Data querying library for testing JavaScript applications.", "scripts": { - "start": "tsc -w", - "format": "prettier src/**/*.ts --write", - "test": "jest -c test/jest.config.ts", - "test:ts": "tsc -p test/tsconfig.test-d.json", - "clean": "rimraf ./lib", - "build": "yarn clean && tsc", - "release": "release publish", - "prepare": "yarn simple-git-hooks init", - "prepublishOnly": "yarn build && yarn test:ts && yarn test" + "dev": "tsdown -w", + "test:node": "vitest", + "test:browser": "playwright test", + "lint": "publint", + "build": "tsdown", + "release": "release publish" + }, + "imports": { + "#/src/*": "./src/*" + }, + "exports": { + ".": "./build/index.mjs", + "./extensions": "./build/extensions/index.mjs", + "./extensions/sync": "./build/extensions/sync.mjs", + "./extensions/persist": "./build/extensions/persist.mjs" }, "files": [ - "lib", - "README.md" + "./build", + "./src", + "./tests" + ], + "keywords": [ + "data", + "query", + "mock", + "model", + "testing", + "fake", + "msw" ], + "license": "MIT", + "author": "Artem Zakharchenko ", + "homepage": "https://github.com/mswjs/data", + "repository": { + "type": "git", + "url": "git+https://github.com/mswjs/data.git" + }, "devDependencies": { - "@commitlint/cli": "^16.0.1", - "@commitlint/config-conventional": "^16.0.0", - "@ossjs/release": "^0.2.1", - "@types/debug": "^4.1.5", - "@types/faker": "^5.5.3", - "@types/jest": "^26.0.22", - "@types/node-fetch": "^2.5.10", - "commitizen": "^4.2.4", - "cz-conventional-changelog": "3.3.0", - "faker": "^5.5.3", - "jest": "^26.6.0", - "msw": "latest", - "node-fetch": "^2.6.1", - "page-with": "^0.4.1", - "prettier": "^2.2.1", - "rimraf": "^3.0.2", - "simple-git-hooks": "^2.7.0", - "ts-jest": "^26.5.5", - "ts-node": "^9.1.1", - "typescript": "4.3.5" + "@ossjs/release": "^0.10.1", + "@playwright/test": "^1.59.1", + "@types/node": "^24.6.2", + "prettier": "^3.8.3", + "publint": "^0.3.18", + "tsdown": "^0.21.9", + "typescript": "^5.9.3", + "vite": "^8.0.8", + "vitest": "^4.1.4", + "zod": "^4.3.6" }, "dependencies": { - "@types/lodash": "^4.14.172", - "@types/md5": "^2.3.0", - "@types/pluralize": "^0.0.29", - "@types/uuid": "^8.3.0", - "date-fns": "^2.21.1", - "debug": "^4.3.1", - "graphql": "^15.5.0", - "lodash": "^4.17.21", - "md5": "^2.3.0", - "outvariant": "^1.2.1", - "pluralize": "^8.0.0", - "strict-event-emitter": "^0.2.0", - "uuid": "^8.3.1" - }, - "optionalDependencies": { - "msw": "latest" - }, - "config": { - "commitizen": { - "path": "./node_modules/cz-conventional-changelog" - } + "@standard-schema/spec": "^1.1.0", + "es-toolkit": "^1.45.1", + "mutative": "^1.3.0", + "outvariant": "^1.4.3", + "rettime": "^0.11.8" } -} +} \ No newline at end of file diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 00000000..e588d7d7 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,12 @@ +import { defineConfig, devices } from '@playwright/test' + +export default defineConfig({ + testDir: './tests', + testMatch: '**/*.browser.test.ts', + workers: 1, + projects: [ + { + use: devices['Desktop Chrome'], + }, + ], +}) diff --git a/playwright.extend.ts b/playwright.extend.ts new file mode 100644 index 00000000..33c26af9 --- /dev/null +++ b/playwright.extend.ts @@ -0,0 +1,91 @@ +import fs from 'node:fs' +import path from 'node:path' +import { invariant } from 'outvariant' +import { test as testBase, expect, type Page } from '@playwright/test' +import { createServer, type ViteDevServer } from 'vite' + +interface Fixtures { + serve: >( + fn: () => Promise, + ) => Promise<{ + url: URL + evaluate: any>( + callback: Callback, + options?: { page: Page }, + ) => Promise> + }> +} + +export { expect } + +export const test = testBase.extend({ + async serve({ page }, use) { + let server: ViteDevServer | undefined + + const directory = path.join( + process.cwd(), + './tests/.tmp', + test.info().testId, + ) + const entryPath = path.join(directory, 'entry.ts') + + await use(async (fn) => { + await fs.promises.mkdir(directory, { recursive: true }) + await fs.promises.writeFile( + entryPath, + `window.__vite_playwright_context__ = await (${fn.toString()})();`, + 'utf8', + ) + + await fs.promises.writeFile( + path.join(directory, 'index.html'), + ` + + + + + + + `, + 'utf8', + ) + + server = await createServer({ + root: directory, + optimizeDeps: { + entries: [entryPath], + }, + build: { + target: 'chrome139', + rollupOptions: { + external: /.+/, + }, + }, + configFile: false, + logLevel: 'error', + }) + + await server.listen() + await page.waitForLoadState('networkidle') + + const url = server.resolvedUrls?.local[0] + invariant(url, 'Failed to spawn Vite dev server') + + return { + url: new URL(url), + async evaluate(callback, options) { + const targetPage = options?.page || page + const context = await targetPage.evaluateHandle( + () => window['__vite_playwright_context__' as keyof typeof window], + ) + return await context.evaluate(callback) + }, + } + }) + + await Promise.all([ + server?.close(), + fs.promises.rm(directory, { recursive: true }), + ]) + }, +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 00000000..5c5f74f5 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,2156 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@standard-schema/spec': + specifier: ^1.1.0 + version: 1.1.0 + es-toolkit: + specifier: ^1.45.1 + version: 1.45.1 + mutative: + specifier: ^1.3.0 + version: 1.3.0 + outvariant: + specifier: ^1.4.3 + version: 1.4.3 + rettime: + specifier: ^0.11.8 + version: 0.11.8 + devDependencies: + '@ossjs/release': + specifier: ^0.10.1 + version: 0.10.1 + '@playwright/test': + specifier: ^1.59.1 + version: 1.59.1 + '@types/node': + specifier: ^24.6.2 + version: 24.6.2 + prettier: + specifier: ^3.8.3 + version: 3.8.3 + publint: + specifier: ^0.3.18 + version: 0.3.18 + tsdown: + specifier: ^0.21.9 + version: 0.21.9(publint@0.3.18)(typescript@5.9.3) + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vite: + specifier: ^8.0.8 + version: 8.0.8(@types/node@24.6.2)(jiti@2.5.1) + vitest: + specifier: ^4.1.4 + version: 4.1.4(@types/node@24.6.2)(vite@8.0.8(@types/node@24.6.2)(jiti@2.5.1)) + zod: + specifier: ^4.3.6 + version: 4.3.6 + +packages: + + '@babel/generator@8.0.0-rc.3': + resolution: {integrity: sha512-em37/13/nR320G4jab/nIIHZgc2Wz2y/D39lxnTyxB4/D/omPQncl/lSdlnJY1OhQcRGugTSIF2l/69o31C9dA==} + engines: {node: ^20.19.0 || >=22.12.0} + + '@babel/helper-string-parser@8.0.0-rc.3': + resolution: {integrity: sha512-AmwWFx1m8G/a5cXkxLxTiWl+YEoWuoFLUCwqMlNuWO1tqAYITQAbCRPUkyBHv1VOFgfjVOqEj6L3u15J5ZCzTA==} + engines: {node: ^20.19.0 || >=22.12.0} + + '@babel/helper-validator-identifier@8.0.0-rc.3': + resolution: {integrity: sha512-8AWCJ2VJJyDFlGBep5GpaaQ9AAaE/FjAcrqI7jyssYhtL7WGV0DOKpJsQqM037xDbpRLHXsY8TwU7zDma7coOw==} + engines: {node: ^20.19.0 || >=22.12.0} + + '@babel/parser@8.0.0-rc.3': + resolution: {integrity: sha512-B20dvP3MfNc/XS5KKCHy/oyWl5IA6Cn9YjXRdDlCjNmUFrjvLXMNUfQq/QUy9fnG2gYkKKcrto2YaF9B32ToOQ==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + '@babel/types@8.0.0-rc.3': + resolution: {integrity: sha512-mOm5ZrYmphGfqVWoH5YYMTITb3cDXsFgmvFlvkvWDMsR9X8RFnt7a0Wb6yNIdoFsiMO9WjYLq+U/FMtqIYAF8Q==} + engines: {node: ^20.19.0 || >=22.12.0} + + '@emnapi/core@1.9.2': + resolution: {integrity: sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==} + + '@emnapi/runtime@1.9.2': + resolution: {integrity: sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@napi-rs/wasm-runtime@1.1.4': + resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@open-draft/deferred-promise@2.2.0': + resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + + '@ossjs/release@0.10.1': + resolution: {integrity: sha512-Djr1DXVTeR476ZdNC0RYPQQuX7Hx+CnEex1AyBBnsizQ7KqsidW99iooDFxT36jy8ReXwdTgIsjP2OSdyjfbLQ==} + engines: {node: '>=20.0.0'} + hasBin: true + + '@oxc-project/types@0.124.0': + resolution: {integrity: sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==} + + '@oxc-project/types@0.126.0': + resolution: {integrity: sha512-oGfVtjAgwQVVpfBrbtk4e1XDyWHRFta6BS3GWVzrF8xYBT2VGQAk39yJS/wFSMrZqoiCU4oghT3Ch0HaHGIHcQ==} + + '@playwright/test@1.59.1': + resolution: {integrity: sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==} + engines: {node: '>=18'} + hasBin: true + + '@pnpm/config.env-replace@1.1.0': + resolution: {integrity: sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==} + engines: {node: '>=12.22.0'} + + '@pnpm/network.ca-file@1.0.2': + resolution: {integrity: sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==} + engines: {node: '>=12.22.0'} + + '@pnpm/npm-conf@2.3.1': + resolution: {integrity: sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==} + engines: {node: '>=12'} + + '@publint/pack@0.1.4': + resolution: {integrity: sha512-HDVTWq3H0uTXiU0eeSQntcVUTPP3GamzeXI41+x7uU9J65JgWQh3qWZHblR1i0npXfFtF+mxBiU2nJH8znxWnQ==} + engines: {node: '>=18'} + + '@quansync/fs@1.0.0': + resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==} + + '@rolldown/binding-android-arm64@1.0.0-rc.15': + resolution: {integrity: sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-android-arm64@1.0.0-rc.16': + resolution: {integrity: sha512-rhY3k7Bsae9qQfOtph2Pm2jZEA+s8Gmjoz4hhmx70K9iMQ/ddeae+xhRQcM5IuVx5ry1+bGfkvMn7D6MJggVSA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.0-rc.15': + resolution: {integrity: sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-arm64@1.0.0-rc.16': + resolution: {integrity: sha512-rNz0yK078yrNn3DrdgN+PKiMOW8HfQ92jQiXxwX8yW899ayV00MLVdaCNeVBhG/TbH3ouYVObo8/yrkiectkcQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.0-rc.15': + resolution: {integrity: sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.0-rc.16': + resolution: {integrity: sha512-r/OmdR00HmD4i79Z//xO06uEPOq5hRXdhw7nzkxQxwSavs3PSHa1ijntdpOiZ2mzOQ3fVVu8C1M19FoNM+dMUQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.0-rc.15': + resolution: {integrity: sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-freebsd-x64@1.0.0-rc.16': + resolution: {integrity: sha512-KcRE5w8h0OnjUatG8pldyD14/CQ5Phs1oxfR+3pKDjboHRo9+MkqQaiIZlZRpsxC15paeXme/I127tUa9TXJ6g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.15': + resolution: {integrity: sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.16': + resolution: {integrity: sha512-bT0guA1bpxEJ/ZhTRniQf7rNF8ybvXOuWbNIeLABaV5NGjx4EtOWBTSRGWFU9ZWVkPOZ+HNFP8RMcBokBiZ0Kg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.15': + resolution: {integrity: sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.16': + resolution: {integrity: sha512-+tHktCHWV8BDQSjemUqm/Jl/TPk3QObCTIjmdDy/nlupcujZghmKK2962LYrqFpWu+ai01AN/REOH3NEpqvYQg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.15': + resolution: {integrity: sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.16': + resolution: {integrity: sha512-3fPzdREH806oRLxpTWW1Gt4tQHs0TitZFOECB2xzCFLPKnSOy90gwA7P29cksYilFO6XVRY1kzga0cL2nRjKPg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15': + resolution: {integrity: sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.16': + resolution: {integrity: sha512-EKwI1tSrLs7YVw+JPJT/G2dJQ1jl9qlTTTEG0V2Ok/RdOenRfBw2PQdLPyjhIu58ocdBfP7vIRN/pvMsPxs/AQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15': + resolution: {integrity: sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.16': + resolution: {integrity: sha512-Uknladnb3Sxqu6SEcqBldQyJUpk8NleooZEc0MbRBJ4inEhRYWZX0NJu12vNf2mqAq7gsofAxHrGghiUYjhaLQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.15': + resolution: {integrity: sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.16': + resolution: {integrity: sha512-FIb8+uG49sZBtLTn+zt1AJ20TqVcqWeSIyoVt0or7uAWesgKaHbiBh6OpA/k9v0LTt+PTrb1Lao133kP4uVxkg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.15': + resolution: {integrity: sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.16': + resolution: {integrity: sha512-RuERhF9/EgWxZEXYWCOaViUWHIboceK4/ivdtQ3R0T44NjLkIIlGIAVAuCddFxsZ7vnRHtNQUrt2vR2n2slB2w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.15': + resolution: {integrity: sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.16': + resolution: {integrity: sha512-mXcXnvd9GpazCxeUCCnZ2+YF7nut+ZOEbE4GtaiPtyY6AkhZWbK70y1KK3j+RDhjVq5+U8FySkKRb/+w0EeUwA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.15': + resolution: {integrity: sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.16': + resolution: {integrity: sha512-3Q2KQxnC8IJOLqXmUMoYwyIPZU9hzRbnHaoV3Euz+VVnjZKcY8ktnNP8T9R4/GGQtb27C/UYKABxesKWb8lsvQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.15': + resolution: {integrity: sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.16': + resolution: {integrity: sha512-tj7XRemQcOcFwv7qhpUxMTBbI5mWMlE4c1Omhg5+h8GuLXzyj8HviYgR+bB2DMDgRqUE+jiDleqSCRjx4aYk/Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.15': + resolution: {integrity: sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.16': + resolution: {integrity: sha512-PH5DRZT+F4f2PTXRXR8uJxnBq2po/xFtddyabTJVJs/ZYVHqXPEgNIr35IHTEa6bpa0Q8Awg+ymkTaGnKITw4g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.0-rc.15': + resolution: {integrity: sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==} + + '@rolldown/pluginutils@1.0.0-rc.16': + resolution: {integrity: sha512-45+YtqxLYKDWQouLKCrpIZhke+nXxhsw+qAHVzHDVwttyBlHNBVs2K25rDXrZzhpTp9w1FlAlvweV1H++fdZoA==} + + '@simple-libs/stream-utils@1.2.0': + resolution: {integrity: sha512-KxXvfapcixpz6rVEB6HPjOUZT22yN6v0vI0urQSk1L8MlEWPDFCZkhw2xmkyoTGYeFw7tWTZd7e3lVzRZRN/EA==} + engines: {node: '>=18'} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + + '@types/chai@5.2.2': + resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} + + '@types/conventional-commits-parser@5.0.2': + resolution: {integrity: sha512-BgT2szDXnVypgpNxOK8aL5SGjUdaQbC++WZNjF1Qge3Og2+zhHj+RWhmehLhYyvQwqAmvezruVfOf8+3m74W+g==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/issue-parser@3.0.5': + resolution: {integrity: sha512-fvOrnb7uS6qRme16tfyxy9SjOgx47Krkt/ilLS7axP3SWtJb9GZlduWX2bAsJOnr1HuCwJh88rCidzCZ1LwuZg==} + + '@types/jsesc@2.5.1': + resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==} + + '@types/node@24.12.2': + resolution: {integrity: sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==} + + '@types/node@24.6.2': + resolution: {integrity: sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang==} + + '@types/semver@7.7.1': + resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.35': + resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + + '@vitest/expect@4.1.4': + resolution: {integrity: sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==} + + '@vitest/mocker@4.1.4': + resolution: {integrity: sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.4': + resolution: {integrity: sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==} + + '@vitest/runner@4.1.4': + resolution: {integrity: sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==} + + '@vitest/snapshot@4.1.4': + resolution: {integrity: sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==} + + '@vitest/spy@4.1.4': + resolution: {integrity: sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==} + + '@vitest/utils@4.1.4': + resolution: {integrity: sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==} + + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + ansis@4.2.0: + resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} + engines: {node: '>=14'} + + args@5.0.3: + resolution: {integrity: sha512-h6k/zfFgusnv3i5TU08KQkVKuCPBtL/PWQbWkHUxvJrZ2nAyeaUupneemcrgn1xmqxPQsPIzwkUhOpoqPDRZuA==} + engines: {node: '>= 6.0.0'} + + argv-formatter@1.0.0: + resolution: {integrity: sha512-F2+Hkm9xFaRg+GkaNnbwXNDV5O6pnCFEmqyhvfC/Ic5LbgOWjJh3L+mN/s91rxVL3znE7DYVpW0GJFT+4YBgWw==} + + ast-kit@3.0.0-beta.1: + resolution: {integrity: sha512-trmleAnZ2PxN/loHWVhhx1qeOHSRXq4TDsBBxq3GqeJitfk3+jTQ+v/C1km/KYq9M7wKqCewMh+/NAvVH7m+bw==} + engines: {node: '>=20.19.0'} + + atomic-sleep@1.0.0: + resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} + engines: {node: '>=8.0.0'} + + birpc@4.0.0: + resolution: {integrity: sha512-LShSxJP0KTmd101b6DRyGBj57LZxSDYWKitQNW/mi8GRMvZb078Uf9+pveax1DrVL89vm7mWe+TovdI/UDOuPw==} + + cac@7.0.0: + resolution: {integrity: sha512-tixWYgm5ZoOD+3g6UTea91eow5z6AAHaho3g0V9CNSNb45gM8SmflpAc+GRd1InC4AqN/07Unrgp56Y94N9hJQ==} + engines: {node: '>=20.19.0'} + + camelcase@5.0.0: + resolution: {integrity: sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==} + engines: {node: '>=6'} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + + cliui@9.0.1: + resolution: {integrity: sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==} + engines: {node: '>=20'} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + + config-chain@1.1.13: + resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} + + conventional-commits-parser@6.4.0: + resolution: {integrity: sha512-tvRg7FIBNlyPzjdG8wWRlPHQJJHI7DylhtRGeU9Lq+JuoPh5BKpPRX83ZdLrvXuOSu5Eo/e7SzOQhU4Hd2Miuw==} + engines: {node: '>=18'} + hasBin: true + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + dateformat@4.6.3: + resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + defu@6.1.7: + resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + dts-resolver@2.1.3: + resolution: {integrity: sha512-bihc7jPC90VrosXNzK0LTE2cuLP6jr0Ro8jk+kMugHReJVLIpHz/xadeq3MhuwyO4TD4OA3L1Q8pBBFRc08Tsw==} + engines: {node: '>=20.19.0'} + peerDependencies: + oxc-resolver: '>=11.0.0' + peerDependenciesMeta: + oxc-resolver: + optional: true + + duplexer2@0.1.4: + resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} + + duplexify@4.1.3: + resolution: {integrity: sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==} + + emoji-regex@10.5.0: + resolution: {integrity: sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg==} + + empathic@2.0.0: + resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} + engines: {node: '>=14'} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + es-module-lexer@2.0.0: + resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} + + es-toolkit@1.45.1: + resolution: {integrity: sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-redact@3.5.0: + resolution: {integrity: sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==} + engines: {node: '>=6'} + + fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-east-asian-width@1.4.0: + resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} + engines: {node: '>=18'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + + git-log-parser@1.2.1: + resolution: {integrity: sha512-PI+sPDvHXNPl5WNOErAK05s3j0lgwUzMN6o8cyQrDaKfT3qd7TmNJKeXX+SknI5I0QhG5fVPAEwSY4tRGDtYoQ==} + + graceful-fs@4.2.10: + resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + + hookable@6.1.1: + resolution: {integrity: sha512-U9LYDy1CwhMCnprUfeAZWZGByVbhd54hwepegYTK7Pi5NvqEj63ifz5z+xukznehT7i6NIZRu89Ay1AZmRsLEQ==} + + import-without-cache@0.3.3: + resolution: {integrity: sha512-bDxwDdF04gm550DfZHgffvlX+9kUlcz32UD0AeBTmVPFiWkrexF2XVmiuFFbDhiFuP8fQkrkvI2KdSNPYWAXkQ==} + engines: {node: '>=20.19.0'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + issue-parser@7.0.1: + resolution: {integrity: sha512-3YZcUUR2Wt1WsapF+S/WiA2WmlW0cWAoPccMqne7AxEBhCdFeTPjfv/Axb8V2gyCgY3nRw+ksZ3xSUX+R47iAg==} + engines: {node: ^18.17 || >=20.6.1} + + jiti@2.5.1: + resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==} + hasBin: true + + joycon@3.1.1: + resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} + engines: {node: '>=10'} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + leven@2.1.0: + resolution: {integrity: sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==} + engines: {node: '>=0.10.0'} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + lodash.capitalize@4.2.1: + resolution: {integrity: sha512-kZzYOKspf8XVX5AvmQF94gQW0lejFVgb80G85bU4ZWzoJ6C03PQg3coYAUpSTpQWelrZELd3XWgHzw4Ck5kaIw==} + + lodash.escaperegexp@4.1.2: + resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + + lodash.uniqby@4.7.0: + resolution: {integrity: sha512-e/zcLx6CSbmaEgFHCA7BnoQKyCtKMxnuWrJygbwPs/AIn+IMKl66L8/s+wBUn5LRw2pZx3bUHibiV1b6aTWIww==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + meow@13.2.0: + resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} + engines: {node: '>=18'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + mri@1.1.4: + resolution: {integrity: sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w==} + engines: {node: '>=4'} + + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + + mutative@1.3.0: + resolution: {integrity: sha512-8MJj6URmOZAV70dpFe1YnSppRTKC4DsMkXQiBDFayLcDI4ljGokHxmpqaBQuDWa4iAxWaJJ1PS8vAmbntjjKmQ==} + engines: {node: '>=14.0'} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + + on-exit-leak-free@0.2.0: + resolution: {integrity: sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg==} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + outvariant@1.4.3: + resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + + package-manager-detector@1.6.0: + resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + pino-abstract-transport@0.5.0: + resolution: {integrity: sha512-+KAgmVeqXYbTtU2FScx1XS3kNyfZ5TrXY07V96QnUSFqo2gAqlvmaxH67Lj7SWazqsMabf+58ctdTcBgnOLUOQ==} + + pino-pretty@7.6.1: + resolution: {integrity: sha512-H7N6ZYkiyrfwBGW9CSjx0uyO9Q2Lyt73881+OTYk8v3TiTdgN92QHrWlEq/LeWw5XtDP64jeSk3mnc6T+xX9/w==} + hasBin: true + + pino-std-serializers@4.0.0: + resolution: {integrity: sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q==} + + pino@7.11.0: + resolution: {integrity: sha512-dMACeu63HtRLmCG8VKdy4cShCPKaYDR4youZqoSWLxl5Gu99HUw8bw75thbPv9Nip+H+QYX8o3ZJbTdVZZ2TVg==} + hasBin: true + + playwright-core@1.59.1: + resolution: {integrity: sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.59.1: + resolution: {integrity: sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==} + engines: {node: '>=18'} + hasBin: true + + postcss@8.5.10: + resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==} + engines: {node: ^10 || ^12 || >=14} + + prettier@3.8.3: + resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} + engines: {node: '>=14'} + hasBin: true + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + process-warning@1.0.0: + resolution: {integrity: sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==} + + proto-list@1.2.4: + resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + + publint@0.3.18: + resolution: {integrity: sha512-JRJFeBTrfx4qLwEuGFPk+haJOJN97KnPuK01yj+4k/Wj5BgoOK5uNsivporiqBjk2JDaslg7qJOhGRnpltGeog==} + engines: {node: '>=18'} + hasBin: true + + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + + quansync@1.0.0: + resolution: {integrity: sha512-5xZacEEufv3HSTPQuchrvV6soaiACMFnq1H8wkVioctoH3TRha9Sz66lOxRwPK/qZj7HPiSveih9yAyh98gvqA==} + + quick-format-unescaped@4.0.4: + resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==} + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + real-require@0.1.0: + resolution: {integrity: sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg==} + engines: {node: '>= 12.13.0'} + + registry-auth-token@5.1.0: + resolution: {integrity: sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==} + engines: {node: '>=14'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + rettime@0.11.8: + resolution: {integrity: sha512-0fERGXktJTyJ+h8fBEiPxHPEFOu0h15JY7JtwrOVqR5K+vb99ho6IyOo7ekLS3h4sJCzIDy4VWKIbZUfe9njmg==} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + rolldown-plugin-dts@0.23.2: + resolution: {integrity: sha512-PbSqLawLgZBGcOGT3yqWBGn4cX+wh2nt5FuBGdcMHyOhoukmjbhYAl8NT9sE4U38Cm9tqLOIQeOrvzeayM0DLQ==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@ts-macro/tsc': ^0.3.6 + '@typescript/native-preview': '>=7.0.0-dev.20260325.1' + rolldown: ^1.0.0-rc.12 + typescript: ^5.0.0 || ^6.0.0 + vue-tsc: ~3.2.0 + peerDependenciesMeta: + '@ts-macro/tsc': + optional: true + '@typescript/native-preview': + optional: true + typescript: + optional: true + vue-tsc: + optional: true + + rolldown@1.0.0-rc.15: + resolution: {integrity: sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + rolldown@1.0.0-rc.16: + resolution: {integrity: sha512-rzi5WqKzEZw3SooTt7cgm4eqIoujPIyGcJNGFL7iPEuajQw7vxMHUkXylu4/vhCkJGXsgRmxqMKXUpT6FEgl0g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-stable-stringify@2.5.0: + resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==} + engines: {node: '>=10'} + + secure-json-parse@2.7.0: + resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + sonic-boom@2.8.0: + resolution: {integrity: sha512-kuonw1YOYYNOve5iHdSahXPOK49GqwA+LZhI6Wz/l0rP57iKyXXIHaRagOBHAPmGwJC6od2Z9zgvZ5loSgMlVg==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + spawn-error-forwarder@1.0.0: + resolution: {integrity: sha512-gRjMgK5uFjbCvdibeGJuy3I5OYz6VLoVdsOJdA6wV0WlfQVLFueoqMxwwYD9RODdgb6oUIvlRlsyFSiQkMKu0g==} + + split2@1.0.0: + resolution: {integrity: sha512-NKywug4u4pX/AZBB1FCPzZ6/7O+Xhz1qMVbzTvvKvikjO99oPN87SkK08mEY9P63/5lWjK+wgOOgApnTg5r6qg==} + + split2@4.2.0: + resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} + engines: {node: '>= 10.x'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@4.1.0: + resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + + stream-combiner2@1.1.1: + resolution: {integrity: sha512-3PnJbYgS56AeWgtKF5jtJRT6uFJe56Z0Hc5Ngg/6sI6rIt8iiMBTa9cvdyFfpMQjaVHr8dusbNeFGIIonxOvKw==} + + stream-shift@1.0.3: + resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + engines: {node: '>=12'} + + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + + thread-stream@0.15.2: + resolution: {integrity: sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA==} + + through2@2.0.5: + resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.1.1: + resolution: {integrity: sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==} + engines: {node: '>=18'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + + traverse@0.6.8: + resolution: {integrity: sha512-aXJDbk6SnumuaZSANd21XAo15ucCDE38H4fkqiGsc3MhCK+wOlZvLP9cB/TvpHT0mOyWgC4Z8EwRlzqYSUzdsA==} + engines: {node: '>= 0.4'} + + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + tsdown@0.21.9: + resolution: {integrity: sha512-tZPv2zMaMnjj9H9h0SDqpSXa9YWVZWHlG46DnSgNTFX6aq001MSI8kuBzJumr/u099nWj+1v5S7rhbnHk5jCHA==} + engines: {node: '>=20.19.0'} + hasBin: true + peerDependencies: + '@arethetypeswrong/core': ^0.18.1 + '@tsdown/css': 0.21.9 + '@tsdown/exe': 0.21.9 + '@vitejs/devtools': '*' + publint: ^0.3.0 + typescript: ^5.0.0 || ^6.0.0 + unplugin-unused: ^0.5.0 + peerDependenciesMeta: + '@arethetypeswrong/core': + optional: true + '@tsdown/css': + optional: true + '@tsdown/exe': + optional: true + '@vitejs/devtools': + optional: true + publint: + optional: true + typescript: + optional: true + unplugin-unused: + optional: true + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + unconfig-core@7.5.0: + resolution: {integrity: sha512-Su3FauozOGP44ZmKdHy2oE6LPjk51M/TRRjHv2HNCWiDvfvCoxC2lno6jevMA91MYAdCdwP05QnWdWpSbncX/w==} + + undici-types@7.13.0: + resolution: {integrity: sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ==} + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + unrun@0.2.36: + resolution: {integrity: sha512-ICAGv44LHSKjCdI4B4rk99lJLHXBweutO4MUwu3cavMlYtXID0Tn5e1Kwe/Uj6BSAuHHXfi1JheFVCYhcXHfAg==} + engines: {node: '>=20.19.0'} + hasBin: true + peerDependencies: + synckit: ^0.11.11 + peerDependenciesMeta: + synckit: + optional: true + + until-async@3.0.2: + resolution: {integrity: sha512-IiSk4HlzAMqTUseHHe3VhIGyuFmN90zMTpD3Z3y8jeQbzLIq500MVM7Jq2vUAnTKAFPJrqwkzr6PoTcPhGcOiw==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vite@8.0.8: + resolution: {integrity: sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.1.4: + resolution: {integrity: sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.4 + '@vitest/browser-preview': 4.1.4 + '@vitest/browser-webdriverio': 4.1.4 + '@vitest/coverage-istanbul': 4.1.4 + '@vitest/coverage-v8': 4.1.4 + '@vitest/ui': 4.1.4 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yargs-parser@22.0.0: + resolution: {integrity: sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + + yargs@18.0.0: + resolution: {integrity: sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=23} + + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + +snapshots: + + '@babel/generator@8.0.0-rc.3': + dependencies: + '@babel/parser': 8.0.0-rc.3 + '@babel/types': 8.0.0-rc.3 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + '@types/jsesc': 2.5.1 + jsesc: 3.1.0 + + '@babel/helper-string-parser@8.0.0-rc.3': {} + + '@babel/helper-validator-identifier@8.0.0-rc.3': {} + + '@babel/parser@8.0.0-rc.3': + dependencies: + '@babel/types': 8.0.0-rc.3 + + '@babel/types@8.0.0-rc.3': + dependencies: + '@babel/helper-string-parser': 8.0.0-rc.3 + '@babel/helper-validator-identifier': 8.0.0-rc.3 + + '@emnapi/core@1.9.2': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.9.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2)': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@tybys/wasm-util': 0.10.1 + optional: true + + '@open-draft/deferred-promise@2.2.0': {} + + '@ossjs/release@0.10.1': + dependencies: + '@open-draft/deferred-promise': 2.2.0 + '@types/conventional-commits-parser': 5.0.2 + '@types/issue-parser': 3.0.5 + '@types/node': 24.12.2 + '@types/semver': 7.7.1 + '@types/yargs': 17.0.35 + ajv: 8.17.1 + conventional-commits-parser: 6.4.0 + get-stream: 6.0.1 + git-log-parser: 1.2.1 + issue-parser: 7.0.1 + outvariant: 1.4.3 + pino: 7.11.0 + pino-pretty: 7.6.1 + publint: 0.3.18 + rc: 1.2.8 + registry-auth-token: 5.1.0 + semver: 7.7.4 + until-async: 3.0.2 + yargs: 18.0.0 + + '@oxc-project/types@0.124.0': {} + + '@oxc-project/types@0.126.0': {} + + '@playwright/test@1.59.1': + dependencies: + playwright: 1.59.1 + + '@pnpm/config.env-replace@1.1.0': {} + + '@pnpm/network.ca-file@1.0.2': + dependencies: + graceful-fs: 4.2.10 + + '@pnpm/npm-conf@2.3.1': + dependencies: + '@pnpm/config.env-replace': 1.1.0 + '@pnpm/network.ca-file': 1.0.2 + config-chain: 1.1.13 + + '@publint/pack@0.1.4': {} + + '@quansync/fs@1.0.0': + dependencies: + quansync: 1.0.0 + + '@rolldown/binding-android-arm64@1.0.0-rc.15': + optional: true + + '@rolldown/binding-android-arm64@1.0.0-rc.16': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.0-rc.15': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.0-rc.16': + optional: true + + '@rolldown/binding-darwin-x64@1.0.0-rc.15': + optional: true + + '@rolldown/binding-darwin-x64@1.0.0-rc.16': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.0-rc.15': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.0-rc.16': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.16': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.16': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.16': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.16': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.16': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.16': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.15': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0-rc.16': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.15': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0-rc.16': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.15': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0-rc.16': + dependencies: + '@emnapi/core': 1.9.2 + '@emnapi/runtime': 1.9.2 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.9.2)(@emnapi/runtime@1.9.2) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.15': + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.16': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.15': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.16': + optional: true + + '@rolldown/pluginutils@1.0.0-rc.15': {} + + '@rolldown/pluginutils@1.0.0-rc.16': {} + + '@simple-libs/stream-utils@1.2.0': {} + + '@standard-schema/spec@1.1.0': {} + + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/chai@5.2.2': + dependencies: + '@types/deep-eql': 4.0.2 + + '@types/conventional-commits-parser@5.0.2': + dependencies: + '@types/node': 24.12.2 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.8': {} + + '@types/issue-parser@3.0.5': {} + + '@types/jsesc@2.5.1': {} + + '@types/node@24.12.2': + dependencies: + undici-types: 7.16.0 + + '@types/node@24.6.2': + dependencies: + undici-types: 7.13.0 + + '@types/semver@7.7.1': {} + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.35': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@vitest/expect@4.1.4': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.2 + '@vitest/spy': 4.1.4 + '@vitest/utils': 4.1.4 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.4(vite@8.0.8(@types/node@24.6.2)(jiti@2.5.1))': + dependencies: + '@vitest/spy': 4.1.4 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.0.8(@types/node@24.6.2)(jiti@2.5.1) + + '@vitest/pretty-format@4.1.4': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.1.4': + dependencies: + '@vitest/utils': 4.1.4 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.4': + dependencies: + '@vitest/pretty-format': 4.1.4 + '@vitest/utils': 4.1.4 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.4': {} + + '@vitest/utils@4.1.4': + dependencies: + '@vitest/pretty-format': 4.1.4 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-regex@6.2.2: {} + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + + ansi-styles@6.2.3: {} + + ansis@4.2.0: {} + + args@5.0.3: + dependencies: + camelcase: 5.0.0 + chalk: 2.4.2 + leven: 2.1.0 + mri: 1.1.4 + + argv-formatter@1.0.0: {} + + ast-kit@3.0.0-beta.1: + dependencies: + '@babel/parser': 8.0.0-rc.3 + estree-walker: 3.0.3 + pathe: 2.0.3 + + atomic-sleep@1.0.0: {} + + birpc@4.0.0: {} + + cac@7.0.0: {} + + camelcase@5.0.0: {} + + chai@6.2.2: {} + + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + + cliui@9.0.1: + dependencies: + string-width: 7.2.0 + strip-ansi: 7.1.2 + wrap-ansi: 9.0.2 + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-name@1.1.3: {} + + colorette@2.0.20: {} + + config-chain@1.1.13: + dependencies: + ini: 1.3.8 + proto-list: 1.2.4 + + conventional-commits-parser@6.4.0: + dependencies: + '@simple-libs/stream-utils': 1.2.0 + meow: 13.2.0 + + convert-source-map@2.0.0: {} + + core-util-is@1.0.3: {} + + dateformat@4.6.3: {} + + deep-extend@0.6.0: {} + + defu@6.1.7: {} + + detect-libc@2.1.2: {} + + dts-resolver@2.1.3: {} + + duplexer2@0.1.4: + dependencies: + readable-stream: 2.3.8 + + duplexify@4.1.3: + dependencies: + end-of-stream: 1.4.5 + inherits: 2.0.4 + readable-stream: 3.6.2 + stream-shift: 1.0.3 + + emoji-regex@10.5.0: {} + + empathic@2.0.0: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + es-module-lexer@2.0.0: {} + + es-toolkit@1.45.1: {} + + escalade@3.2.0: {} + + escape-string-regexp@1.0.5: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + expect-type@1.3.0: {} + + fast-deep-equal@3.1.3: {} + + fast-redact@3.5.0: {} + + fast-safe-stringify@2.1.1: {} + + fast-uri@3.1.0: {} + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + fsevents@2.3.2: + optional: true + + fsevents@2.3.3: + optional: true + + get-caller-file@2.0.5: {} + + get-east-asian-width@1.4.0: {} + + get-stream@6.0.1: {} + + get-tsconfig@4.14.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + git-log-parser@1.2.1: + dependencies: + argv-formatter: 1.0.0 + spawn-error-forwarder: 1.0.0 + split2: 1.0.0 + stream-combiner2: 1.1.1 + through2: 2.0.5 + traverse: 0.6.8 + + graceful-fs@4.2.10: {} + + has-flag@3.0.0: {} + + hookable@6.1.1: {} + + import-without-cache@0.3.3: {} + + inherits@2.0.4: {} + + ini@1.3.8: {} + + isarray@1.0.0: {} + + issue-parser@7.0.1: + dependencies: + lodash.capitalize: 4.2.1 + lodash.escaperegexp: 4.1.2 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.uniqby: 4.7.0 + + jiti@2.5.1: + optional: true + + joycon@3.1.1: {} + + jsesc@3.1.0: {} + + json-schema-traverse@1.0.0: {} + + leven@2.1.0: {} + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + lodash.capitalize@4.2.1: {} + + lodash.escaperegexp@4.1.2: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + + lodash.uniqby@4.7.0: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + meow@13.2.0: {} + + minimist@1.2.8: {} + + mri@1.1.4: {} + + mri@1.2.0: {} + + mutative@1.3.0: {} + + nanoid@3.3.11: {} + + obug@2.1.1: {} + + on-exit-leak-free@0.2.0: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + outvariant@1.4.3: {} + + package-manager-detector@1.6.0: {} + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@4.0.3: {} + + picomatch@4.0.4: {} + + pino-abstract-transport@0.5.0: + dependencies: + duplexify: 4.1.3 + split2: 4.2.0 + + pino-pretty@7.6.1: + dependencies: + args: 5.0.3 + colorette: 2.0.20 + dateformat: 4.6.3 + fast-safe-stringify: 2.1.1 + joycon: 3.1.1 + on-exit-leak-free: 0.2.0 + pino-abstract-transport: 0.5.0 + pump: 3.0.3 + readable-stream: 3.6.2 + rfdc: 1.4.1 + secure-json-parse: 2.7.0 + sonic-boom: 2.8.0 + strip-json-comments: 3.1.1 + + pino-std-serializers@4.0.0: {} + + pino@7.11.0: + dependencies: + atomic-sleep: 1.0.0 + fast-redact: 3.5.0 + on-exit-leak-free: 0.2.0 + pino-abstract-transport: 0.5.0 + pino-std-serializers: 4.0.0 + process-warning: 1.0.0 + quick-format-unescaped: 4.0.4 + real-require: 0.1.0 + safe-stable-stringify: 2.5.0 + sonic-boom: 2.8.0 + thread-stream: 0.15.2 + + playwright-core@1.59.1: {} + + playwright@1.59.1: + dependencies: + playwright-core: 1.59.1 + optionalDependencies: + fsevents: 2.3.2 + + postcss@8.5.10: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prettier@3.8.3: {} + + process-nextick-args@2.0.1: {} + + process-warning@1.0.0: {} + + proto-list@1.2.4: {} + + publint@0.3.18: + dependencies: + '@publint/pack': 0.1.4 + package-manager-detector: 1.6.0 + picocolors: 1.1.1 + sade: 1.8.1 + + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + quansync@1.0.0: {} + + quick-format-unescaped@4.0.4: {} + + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + real-require@0.1.0: {} + + registry-auth-token@5.1.0: + dependencies: + '@pnpm/npm-conf': 2.3.1 + + require-from-string@2.0.2: {} + + resolve-pkg-maps@1.0.0: {} + + rettime@0.11.8: {} + + rfdc@1.4.1: {} + + rolldown-plugin-dts@0.23.2(rolldown@1.0.0-rc.16)(typescript@5.9.3): + dependencies: + '@babel/generator': 8.0.0-rc.3 + '@babel/helper-validator-identifier': 8.0.0-rc.3 + '@babel/parser': 8.0.0-rc.3 + '@babel/types': 8.0.0-rc.3 + ast-kit: 3.0.0-beta.1 + birpc: 4.0.0 + dts-resolver: 2.1.3 + get-tsconfig: 4.14.0 + obug: 2.1.1 + picomatch: 4.0.4 + rolldown: 1.0.0-rc.16 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - oxc-resolver + + rolldown@1.0.0-rc.15: + dependencies: + '@oxc-project/types': 0.124.0 + '@rolldown/pluginutils': 1.0.0-rc.15 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-rc.15 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.15 + '@rolldown/binding-darwin-x64': 1.0.0-rc.15 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.15 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.15 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.15 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.15 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.15 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.15 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.15 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.15 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.15 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.15 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.15 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.15 + + rolldown@1.0.0-rc.16: + dependencies: + '@oxc-project/types': 0.126.0 + '@rolldown/pluginutils': 1.0.0-rc.16 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-rc.16 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.16 + '@rolldown/binding-darwin-x64': 1.0.0-rc.16 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.16 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.16 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.16 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.16 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.16 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.16 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.16 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.16 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.16 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.16 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.16 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.16 + + sade@1.8.1: + dependencies: + mri: 1.2.0 + + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + + safe-stable-stringify@2.5.0: {} + + secure-json-parse@2.7.0: {} + + semver@7.7.4: {} + + siginfo@2.0.0: {} + + sonic-boom@2.8.0: + dependencies: + atomic-sleep: 1.0.0 + + source-map-js@1.2.1: {} + + spawn-error-forwarder@1.0.0: {} + + split2@1.0.0: + dependencies: + through2: 2.0.5 + + split2@4.2.0: {} + + stackback@0.0.2: {} + + std-env@4.1.0: {} + + stream-combiner2@1.1.1: + dependencies: + duplexer2: 0.1.4 + readable-stream: 2.3.8 + + stream-shift@1.0.3: {} + + string-width@7.2.0: + dependencies: + emoji-regex: 10.5.0 + get-east-asian-width: 1.4.0 + strip-ansi: 7.1.2 + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + + strip-json-comments@2.0.1: {} + + strip-json-comments@3.1.1: {} + + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + + thread-stream@0.15.2: + dependencies: + real-require: 0.1.0 + + through2@2.0.5: + dependencies: + readable-stream: 2.3.8 + xtend: 4.0.2 + + tinybench@2.9.0: {} + + tinyexec@1.1.1: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tinyrainbow@3.1.0: {} + + traverse@0.6.8: {} + + tree-kill@1.2.2: {} + + tsdown@0.21.9(publint@0.3.18)(typescript@5.9.3): + dependencies: + ansis: 4.2.0 + cac: 7.0.0 + defu: 6.1.7 + empathic: 2.0.0 + hookable: 6.1.1 + import-without-cache: 0.3.3 + obug: 2.1.1 + picomatch: 4.0.4 + rolldown: 1.0.0-rc.16 + rolldown-plugin-dts: 0.23.2(rolldown@1.0.0-rc.16)(typescript@5.9.3) + semver: 7.7.4 + tinyexec: 1.1.1 + tinyglobby: 0.2.16 + tree-kill: 1.2.2 + unconfig-core: 7.5.0 + unrun: 0.2.36 + optionalDependencies: + publint: 0.3.18 + typescript: 5.9.3 + transitivePeerDependencies: + - '@ts-macro/tsc' + - '@typescript/native-preview' + - oxc-resolver + - synckit + - vue-tsc + + tslib@2.8.1: + optional: true + + typescript@5.9.3: {} + + unconfig-core@7.5.0: + dependencies: + '@quansync/fs': 1.0.0 + quansync: 1.0.0 + + undici-types@7.13.0: {} + + undici-types@7.16.0: {} + + unrun@0.2.36: + dependencies: + rolldown: 1.0.0-rc.16 + + until-async@3.0.2: {} + + util-deprecate@1.0.2: {} + + vite@8.0.8(@types/node@24.6.2)(jiti@2.5.1): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.10 + rolldown: 1.0.0-rc.15 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 24.6.2 + fsevents: 2.3.3 + jiti: 2.5.1 + + vitest@4.1.4(@types/node@24.6.2)(vite@8.0.8(@types/node@24.6.2)(jiti@2.5.1)): + dependencies: + '@vitest/expect': 4.1.4 + '@vitest/mocker': 4.1.4(vite@8.0.8(@types/node@24.6.2)(jiti@2.5.1)) + '@vitest/pretty-format': 4.1.4 + '@vitest/runner': 4.1.4 + '@vitest/snapshot': 4.1.4 + '@vitest/spy': 4.1.4 + '@vitest/utils': 4.1.4 + es-module-lexer: 2.0.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 4.1.0 + tinybench: 2.9.0 + tinyexec: 1.1.1 + tinyglobby: 0.2.15 + tinyrainbow: 3.1.0 + vite: 8.0.8(@types/node@24.6.2)(jiti@2.5.1) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.6.2 + transitivePeerDependencies: + - msw + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.1.2 + + wrappy@1.0.2: {} + + xtend@4.0.2: {} + + y18n@5.0.8: {} + + yargs-parser@22.0.0: {} + + yargs@18.0.0: + dependencies: + cliui: 9.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + string-width: 7.2.0 + y18n: 5.0.8 + yargs-parser: 22.0.0 + + zod@4.3.6: {} diff --git a/release.config.json b/release.config.json new file mode 100644 index 00000000..3aae004e --- /dev/null +++ b/release.config.json @@ -0,0 +1,9 @@ +{ + "$schema": "./node_modules/@ossjs/release/schema.json", + "profiles": [ + { + "name": "latest", + "use": "NPM_CONFIG_PROVENANCE=true pnpm publish --no-git-checks --access=public" + } + ] +} diff --git a/simple-git-hooks.js b/simple-git-hooks.js deleted file mode 100644 index e3a56919..00000000 --- a/simple-git-hooks.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - 'prepare-commit-msg': `grep -qE '^[^#]' .git/COMMIT_EDITMSG || (exec < /dev/tty && yarn cz --hook || true)`, - 'commit-msg': 'yarn commitlint --edit $1', -} diff --git a/src/collection.ts b/src/collection.ts new file mode 100644 index 00000000..83ed7898 --- /dev/null +++ b/src/collection.ts @@ -0,0 +1,995 @@ +import type { StandardSchemaV1 } from '@standard-schema/spec' +import { get } from 'es-toolkit/compat' +import { apply, create as createDraft, type Draft, type Patch } from 'mutative' +import { invariant, InvariantError } from 'outvariant' +import { Logger } from '#/src/logger.js' +import { createHooksEmitter, type HookEventMap } from '#/src/hooks.js' +import { Query } from '#/src/query.js' +import { + createRelationBuilder, + Relation, + type RelationsFunction, +} from '#/src/relation.js' +import { + cloneWithInternals, + definePropertyAtPath, + isObject, + isRecord, + toDeepEntries, +} from '#/src/utils.js' +import { type SortOptions, sortResults } from '#/src/sort.js' +import type { Extension } from '#/src/extensions/index.js' +import { OperationError, OperationErrorCodes } from '#/src/errors.js' +import { TypedEvent, type Emitter } from 'rettime' + +let collectionsCreated = 0 + +export type CollectionOptions = { + /** + * A [Standard Schema](https://standardschema.dev/) describing the records in this collection. + */ + schema: Schema + /** + * Extensions to apply to this collection. + */ + extensions?: Array +} + +export interface PaginationOptions { + /** + * A reference to a record to use as a cursor to start the querying from. + */ + cursor?: RecordType> + /** + * A number of matching records to take (after `skip`, if any). + */ + take?: number + /** + * A number of matching records to skip. + */ + skip?: number +} + +export interface UpdateOptions { + data: UpdateFunction +} + +interface StrictOptions { + /** + * Throws an error if no records match the given query. + */ + strict?: Strict +} + +export type UpdateFunction = (draft: Draft) => Promise | void + +export type RecordType> = V & { + [kPrimaryKey]: string + [kRelationMap]: Map +} + +export const kCollectionId = Symbol('kCollectionId') +export const kPrimaryKey = Symbol('kPrimaryKey') +export const kRelationMap = Symbol('kRelationMap') + +/** + * A collection of data. + * @example + * const users = new Collection({ schema: userSchema }) + */ +export class Collection { + #records: Array>> + #logger: Logger + + private [kCollectionId]: number + + public hooks: Emitter> + + constructor(private readonly options: CollectionOptions) { + this[kCollectionId] = this.#generateCollectionId() + + this.#logger = new Logger('Collection').extend(this[kCollectionId]) + this.#records = [] + + this.hooks = createHooksEmitter() + this.options.extensions?.forEach((extension) => extension.extend(this)) + } + + /** + * Creates a new record with the given values. + * @param initialValues Initial values for the new record. + * @return The created record. + * + * @example + * await users.create({ id: 1, name: 'John' }) + */ + public async create( + initialValues: StandardSchemaV1.InferInput, + ): Promise>> { + let logger = this.#logger.extend('create') + logger.log('initial values:', initialValues) + + const { sanitizedInitialValues, restoreProperties } = + this.#sanitizeInitialValues(initialValues) + + const validationResult = await this.options.schema['~standard'].validate( + sanitizedInitialValues, + ) + + if (validationResult.issues) { + console.error(validationResult.issues) + + throw new OperationError( + 'Failed to create a new record with initial values: does not match the schema. Please see the schema validation errors above.', + OperationErrorCodes.INVALID_INITIAL_VALUES, + ) + } + + let record = validationResult.value as RecordType + + invariant.as( + OperationError.for(OperationErrorCodes.INVALID_INITIAL_VALUES), + typeof record === 'object', + 'Failed to create a record with initial values (%j): expected the record to be an object or an array', + initialValues, + ) + + restoreProperties(record) + + // Generate random primary key for every record. + const primaryKey = + (isObject(initialValues) && + initialValues[kPrimaryKey as keyof typeof initialValues]) || + crypto.randomUUID() + + Object.defineProperties(record, { + [kPrimaryKey]: { + enumerable: false, + configurable: false, + value: primaryKey, + }, + [kRelationMap]: { + enumerable: false, + configurable: false, + value: new Map>(), + }, + }) + + logger = logger.extend(primaryKey) + logger.log('symbols defined!', record[kRelationMap]) + + if (this.hooks.listenerCount('create') > 0) { + await this.hooks.emitAsPromise( + new TypedEvent('create', { data: { record, initialValues } }), + ) + } + logger.log('create hooks done!') + + this.#records.push(record) + logger.log('create done!', record) + + return record + } + + /** + * Creates multiple records using the given initial values factory. + * @param count Number of records to create. + * @param initialValuesFactory Factory function to generate initial values for each record. + * @return Array of created records. + * + * @example + * await users.createMany(5, (index) => ({ id: index + 1})) + */ + public async createMany( + count: number, + initialValuesFactory: ( + index: number, + ) => StandardSchemaV1.InferInput, + ): Promise>>> { + const pendingPromises: Array> = [] + + for (let i = 0; i < count; i++) { + pendingPromises.push(this.create(initialValuesFactory(i))) + } + + return await Promise.all(pendingPromises).catch((error) => { + throw new OperationError( + 'Failed to execute "createMany" on collection: unexpected error', + OperationErrorCodes.UNEXPECTED_ERROR, + error, + ) + }) + } + + /** + * Returns the first record matching the query. + * If no query is provided, returns the first record in the collection. + * @example + * users.findFirst((q) => q.where({ id: 123 })) + */ + public findFirst( + predicate?: + | ((query: Query>>) => Query) + | Query>>, + options?: StrictOptions, + ): Strict extends true + ? RecordType> + : RecordType> | undefined { + if (predicate == null) { + const firstRecord = this.#records[0] + + invariant.as( + OperationError.for(OperationErrorCodes.STRICT_QUERY_WITHOUT_RESULTS), + options?.strict ? firstRecord != null : true, + 'Failed to execute "findFirst" on collection without a query: the collection is empty', + ) + + return firstRecord! + } + + const result = this.#query( + predicate instanceof Query ? predicate : predicate(new Query()), + ).next().value + + invariant.as( + OperationError.for(OperationErrorCodes.STRICT_QUERY_WITHOUT_RESULTS), + options?.strict ? result != null : true, + 'Failed to execute "findFirst" on collection: no record found matching the query', + ) + + return result! + } + + /** + * Returns all records matching the query. + * If no query is provided, returns all records in the collection. + * @example + * users.findMany((q) => q.where({ subscribed: false })) + */ + public findMany( + predicate?: + | ((query: Query>>) => Query) + | Query>>, + options?: PaginationOptions & SortOptions & StrictOptions, + ): Array>> { + const query = + predicate == null + ? new Query(() => true) + : predicate instanceof Query + ? predicate + : predicate(new Query()) + + const results = Array.from(this.#query(query, options)).filter( + (result) => !!result, + ) + + invariant.as( + OperationError.for(OperationErrorCodes.STRICT_QUERY_WITHOUT_RESULTS), + options?.strict ? results.length > 0 : true, + 'Failed to execute "findMany" on collection: no records found matching the query', + ) + + if (options?.orderBy) { + sortResults(options, results) + } + + return results + } + + /** + * Updates the first record matching the query. + * Returns the updated record. + * @example + * await users.update( + * (q) => q.where({ name: 'John' }), + * { + * data(user) { + * user.name = 'Johnatan' + * } + * } + * ) + */ + public async update( + predicate: + | ((query: Query>>) => Query) + | Query>> + | RecordType>, + options: UpdateOptions> & + StrictOptions, + ): Promise< + Strict extends true + ? RecordType> + : RecordType> | undefined + > { + const prevRecord = this.findFirst( + isRecord(predicate) + ? new Query((record) => { + return record[kPrimaryKey] === predicate[kPrimaryKey] + }) + : predicate, + ) + + if (prevRecord == null) { + invariant.as( + OperationError.for(OperationErrorCodes.STRICT_QUERY_WITHOUT_RESULTS), + !options.strict, + 'Failed to execute "update" on collection: no record found matching the query', + ) + + return undefined! + } + + const nextRecord = await this.#produceRecord(prevRecord, options.data) + this.#replaceRecord(prevRecord, nextRecord) + + return nextRecord + } + + /** + * Updates all records matching the query. + * Resolves to the list of updated records. + * @example + * await users.updateMany( + * (q) => q.where({ subscribed: false }), + * { + * data(user) { + * user.subscribed = true + * } + * } + * ) + */ + public async updateMany( + predicate: + | ((query: Query>>) => Query) + | Query>>, + options: UpdateOptions> & + SortOptions & + StrictOptions, + ): Promise>>> { + const prevRecords = this.findMany(predicate) + + if (prevRecords.length === 0) { + invariant.as( + OperationError.for(OperationErrorCodes.STRICT_QUERY_WITHOUT_RESULTS), + !options.strict, + 'Failed to execute "updateMany" on collection: no records found matching the query', + ) + + return [] + } + + const nextRecords = [] + + for (const prevRecord of prevRecords) { + const nextRecord = await this.#produceRecord(prevRecord, options.data) + this.#replaceRecord(prevRecord, nextRecord) + nextRecords.push(nextRecord) + } + + if (options.orderBy) { + sortResults(options, nextRecords) + } + + return nextRecords + } + + /** + * Deletes the first record matching the query. + * @example + * users.delete((q) => q.where({ id: 123 })) + */ + public delete( + predicate: + | ((query: Query>>) => Query) + | Query>> + | RecordType>, + options?: StrictOptions, + ): Strict extends true + ? RecordType> + : RecordType> | undefined { + if (isRecord(predicate)) { + this.#deleteRecord(predicate) + return predicate + } + + const record = this.findFirst(predicate) + + if (record == null) { + invariant.as( + OperationError.for(OperationErrorCodes.STRICT_QUERY_WITHOUT_RESULTS), + !options?.strict, + 'Failed to execute "delete" on collection: no record found matching the query', + ) + + return undefined! + } + + this.#deleteRecord(record) + return record + } + + /** + * Deletes all records matching the query. + * @example + * users.deleteMany((q) => q.where({ subscribed: false })) + */ + public deleteMany( + predicate: + | ((query: Query>>) => Query) + | Query>>, + options?: SortOptions & StrictOptions, + ): Array>> { + /** + * @note Do NOT forward the sorting options to the querying phase + * so the results are returned in the order they are present in the store. + * That way, we can delete them right-to-left correctly. + */ + const records = this.findMany(predicate) + + for (let i = records.length - 1; i >= 0; i--) { + this.#deleteRecord(records[i]!) + } + + if (records.length === 0) { + invariant.as( + OperationError.for(OperationErrorCodes.STRICT_QUERY_WITHOUT_RESULTS), + !options?.strict, + 'Failed to execute "deleteMany" on collection: no records found matching the query', + ) + + return [] + } + + if (options?.orderBy) { + sortResults(options, records) + } + + return records + } + + /** + * Returns the total number of records in this collection. + * @example + * const users = new Collection({ schema }) + * await users.create({ id: 1, name: 'John' }) + * users.count() // 1 + */ + public count(): number { + return this.#records.length + } + + /** + * Returns a list of all records from this collection. + */ + public all(): Array>> { + /** + * @note Preserve exact record references so they might be used + * when querying (must contain primary keys). + */ + return this.#records + } + + /** + * Deletes all the records in this collection. + */ + public clear(): void { + for (const record of this.#records) { + this.#deleteRecord(record) + } + + this.#records.length = 0 + } + + /** + * Defines relations for the records in this collection. + * @example + * users.defineRelations(({ many }) => ({ + * posts: many(posts), + * })) + */ + public defineRelations( + resolver: RelationsFunction>, + ) { + let logger = this.#logger.extend('defineRelations') + logger.log('defining relations...') + + const relations = toDeepEntries<() => Relation>( + resolver(createRelationBuilder(this)) as any, + ) + logger.log('relations declaration:', relations) + + const initializeRelations = ( + record: RecordType, + initialValues: StandardSchemaV1.InferInput = record, + ) => { + for (const [path, createRelation] of relations) { + logger.log(`initializing relation for "${path.join('.')}"...`) + + const relation = createRelation() + relation.initialize(record, path as Array, initialValues) + + logger.log('relation initialized!', relation) + } + } + + // Initialize relations for the existing records that were created + // before these relations were defined. + for (const record of this.#records) { + initializeRelations(record) + } + + // Initialize relations for all records created from now on. + this.hooks.earlyOn('create', (event) => { + initializeRelations(event.data.record, event.data.initialValues) + }) + } + + /** + * Sanitizes the given object so it can be accepted as the input to Standard Schema validation. + * This removes getters to prevent potentially infinite object references in self-referencing + * relations. This also drops the internal symbols but gives a function to restore them back. + */ + #sanitizeInitialValues(initialValues: unknown) { + const propertiesToRestore: Array<{ + path: Array + descriptor: PropertyDescriptor + }> = [] + + // Track visited records by primary key to detect cycles + // in self-referencing relations. Only strip relation values + // when revisiting a record (i.e. an actual cycle), not for + // all nested records indiscriminately. + const visited = new Set() + + const sanitize = ( + value: unknown, + path: Array = [], + ): unknown => { + if (Array.isArray(value)) { + return value.map((value, index) => sanitize(value, path.concat(index))) + } + + if (isObject(value)) { + const record = isRecord(value) ? value : undefined + const isRevisit = record != null && visited.has(record[kPrimaryKey]) + + if (record && !isRevisit) { + visited.add(record[kPrimaryKey]) + } + + const relations = record ? record[kRelationMap] : undefined + + return Object.fromEntries( + Reflect.ownKeys(value).map((key) => { + const childValue = value[key as keyof typeof value] + const childPath = path.concat(key) + + if (typeof key === 'symbol') { + /** + * @note Preserve primary keys on sanitized initial values. + * Otherwise, internal symbols are stripped off and record references are lost. + * This is curcial when handling relations for records that were created + * before the relation was defined. + */ + if (key === kPrimaryKey) { + propertiesToRestore.push({ + path: childPath, + descriptor: Object.getOwnPropertyDescriptor(value, key)!, + }) + } + return [key, childValue] + } + + const relation = relations?.get(key) + + // Only strip relation values when revisiting a record + // to break self-referencing cycles. Non-circular nested + // relations are left intact for proper schema validation. + if (isRevisit && relation && childValue != null) { + propertiesToRestore.push({ + path: childPath, + descriptor: Object.getOwnPropertyDescriptor(value, key)!, + }) + return [key, relation.getDefaultValue()] + } + + return [key, sanitize(childValue, childPath)] + }), + ) + } + + return value + } + + const sanitizedInitialValues = sanitize(initialValues) + + return { + sanitizedInitialValues, + /** + * Restores record properties that were stripped off during the sanitization + * (e.g. relational properties, internal symbols of records given as initial value, etc). + */ + restoreProperties(record: RecordType): void { + for (const { path, descriptor } of propertiesToRestore) { + definePropertyAtPath(record, path, descriptor) + } + }, + } + } + + *#query( + query: Query>>, + options: PaginationOptions = { take: Infinity }, + ): Generator< + RecordType> | undefined, + undefined, + RecordType> | undefined + > { + const { take, cursor, skip } = options + + invariant( + skip !== undefined ? Number.isInteger(skip) && skip >= 0 : true, + 'Failed to query the collection: expected the "skip" pagination option to be a number larger or equal to 0 but got %j', + skip, + ) + + let taken = 0 + let skipped = 0 + + // if (cursor != null) { + // const cursorIndex = store.findIndex((record) => { + // return record[kPrimaryKey] === cursor[kPrimaryKey] + // }) + + // if (cursorIndex === -1) { + // return + // } + + // store = store.slice(cursorIndex + 1) + // } + + const shouldTake = Math.abs(take ?? Infinity) + const delta = take && take < 0 ? -1 : 1 + let start = delta === 1 ? 0 : this.#records.length - 1 + const end = delta === 1 ? this.#records.length : -1 + + if (cursor != null) { + const cursorIndex = this.#records.findIndex((record) => { + return record[kPrimaryKey] === cursor[kPrimaryKey] + }) + + if (cursorIndex === -1) { + return + } + + start = cursorIndex + } + + for (let i = start; i !== end; i += delta) { + const record = this.#records[i] + + if (record != null && query.test(record)) { + if (skip != null) { + if (skipped < skip) { + skipped++ + continue + } + } + + yield record + taken++ + } + + if (taken >= shouldTake) { + break + } + } + } + + /** + * Returns the index of the given record in this collection. + * Performs a primary key-based lookup instead of a reference lookup + * because certain references (like root-level arrays) might become stale + * after updates, but will retain their primary keys. + */ + #indexOf(record: RecordType): number { + return this.#records.findIndex((existingRecord) => { + return existingRecord[kPrimaryKey] === record[kPrimaryKey] + }) + } + + /** + * Replaces the given record with the next version of it. + */ + #replaceRecord(prevRecord: RecordType, nextRecord: RecordType): void { + const index = this.#indexOf(prevRecord) + + invariant( + index !== -1, + 'Failed to replace record "%j" with "%j": previous record not found', + prevRecord, + nextRecord, + ) + + this.#records[index] = nextRecord + } + + /** + * Deletes the given record from the collection. + */ + #deleteRecord(record: RecordType): void { + const index = this.#indexOf(record) + + if (index !== -1) { + const deleteEvent = new TypedEvent('delete', { + data: { deletedRecord: record }, + }) + this.hooks.emit(deleteEvent) + + if (!deleteEvent.defaultPrevented) { + this.#records.splice(index, 1) + } + } + } + + /** + * Produces the next version of the given record by applying the `data` changes to it. + * Re-applies the schema to the end record to ensure validity and apply user-defined transforms. + */ + async #produceRecord( + prevRecord: RecordType>, + updateData: UpdateOptions>['data'], + ): Promise>> { + const logger = this.#logger.extend('produceRecord') + logger.log('updating the record with options:', prevRecord, updateData) + + /** + * @note Clone the previous record, preserving the symbols (so it's considered a record) + * but stripping off relational keys (getters) to preserve the values of foreign records + * at the moment of update. + */ + const frozenPrevRecord = cloneWithInternals( + prevRecord, + ({ key, descriptor }) => { + return typeof key === 'symbol' && descriptor.get == null + }, + ) + + invariant( + isRecord(frozenPrevRecord), + 'Failed to update a record (%j): frozen previous record copy is not a record', + prevRecord, + ) + + /** + * @note Build a draft input where relational getters are replaced with + * plain data descriptors holding their resolved values. `mutative` cannot + * observe mutations through getters (https://github.com/unadlib/mutative/issues/157), + * so a `push` on `draft.posts` is lost unless `posts` is a plain property. + * Live foreign record references (with their internal symbols) are preserved. + */ + const draftInput = this.#buildDraftInput(prevRecord) + + const [maybeNextRecord, patches, inversePatches] = await createDraft( + draftInput, + updateData, + { + strict: false, + enablePatches: true, + }, + ) + + Object.defineProperties(maybeNextRecord, { + [kPrimaryKey]: { + value: prevRecord[kPrimaryKey], + enumerable: false, + configurable: false, + }, + [kRelationMap]: { + value: prevRecord[kRelationMap], + enumerable: false, + configurable: false, + }, + }) + + invariant( + isRecord(maybeNextRecord), + 'Failed to update a record (%j): a record produced by the draft is not a record', + prevRecord, + ) + + // Route the updates produces by the draft through the hooks + // so the hooks could reverse some of them. + const patchesToUndo: Array = [] + + /** + * @note Collapse per-index patches under a relation path into a single + * relation-level event. `draft.posts.push(x)` emits `{path: ['posts', N]}`, + * but relation handlers expect `{path: ['posts'], nextValue: }`. + */ + const relationPaths: Array> = [] + for (const serializedPath of prevRecord[kRelationMap].keys()) { + relationPaths.push(serializedPath.split('.')) + } + + type RelationPatchGroup = { + relationPath: Array + patchIndices: Array + } + const relationGroups = new Map() + const passthroughIndices: Array = [] + + for (let i = 0; i < patches.length; i++) { + const patch = patches[i] + if (!patch) { + continue + } + + const matchingRelationPath = relationPaths.find((relationPath) => { + if (patch.path.length !== relationPath.length + 1) { + return false + } + if (!relationPath.every((key, index) => key === patch.path[index])) { + return false + } + const nextSegment = patch.path[relationPath.length] + return typeof nextSegment === 'number' || nextSegment === 'length' + }) + + if (matchingRelationPath) { + const groupKey = matchingRelationPath.join('.') + const group = relationGroups.get(groupKey) ?? { + relationPath: matchingRelationPath, + patchIndices: [], + } + group.patchIndices.push(i) + relationGroups.set(groupKey, group) + } else { + passthroughIndices.push(i) + } + } + + for (const i of passthroughIndices) { + const patch = patches[i]! + + const updateEvent = new TypedEvent('update', { + data: { + prevRecord: frozenPrevRecord, + nextRecord: maybeNextRecord, + path: patch.path, + prevValue: get(prevRecord, patch.path), + nextValue: patch.value, + }, + }) + + this.hooks.emit(updateEvent) + + if (updateEvent.defaultPrevented) { + const inversePatch = inversePatches[i] + + invariant( + inversePatch != null, + 'Failed to update a record (%j): missing inverse patch at index %d', + prevRecord, + i, + ) + + patchesToUndo.push(inversePatch) + } + } + + for (const group of relationGroups.values()) { + const updateEvent = new TypedEvent('update', { + data: { + prevRecord: frozenPrevRecord, + nextRecord: maybeNextRecord, + path: group.relationPath, + prevValue: get(prevRecord, group.relationPath), + nextValue: get(maybeNextRecord, group.relationPath), + }, + }) + + this.hooks.emit(updateEvent) + + if (updateEvent.defaultPrevented) { + for (const i of group.patchIndices) { + const inversePatch = inversePatches[i] + + invariant( + inversePatch != null, + 'Failed to update a record (%j): missing inverse patch at index %d', + prevRecord, + i, + ) + + patchesToUndo.push(inversePatch) + } + } + } + + const nextRecord = + patchesToUndo.length > 0 + ? apply(maybeNextRecord, patchesToUndo) + : maybeNextRecord + + logger.log('re-applying the schema...') + const { sanitizedInitialValues } = this.#sanitizeInitialValues(nextRecord) + const validationResult = await this.options.schema['~standard'].validate( + sanitizedInitialValues, + ) + + if (validationResult.issues) { + console.error(validationResult.issues) + throw new InvariantError( + 'Failed to update record (%j): resulting record does not match the schema', + frozenPrevRecord, + ) + } + + const finalRecord = validationResult.value as RecordType + logger.log('schema re-applied!') + + const descriptors = Object.getOwnPropertyDescriptors(prevRecord) + for (const key of Reflect.ownKeys(descriptors)) { + const descriptor = descriptors[key as keyof typeof descriptors] + if (typeof key === 'symbol' || typeof descriptor.get === 'function') { + Object.defineProperty(finalRecord, key, descriptor) + } + } + + return finalRecord + } + + /** + * Build a draftable copy of the record where relational getters are replaced + * with data descriptors holding their resolved values. `mutative` cannot + * observe mutations through getters (https://github.com/unadlib/mutative/issues/157), + * so a `push` on `draft.posts` is lost unless `posts` is a plain property. + * Live foreign record references (with their internal symbols) are preserved + * so downstream hooks can resolve primary keys on the drafted elements. + */ + #buildDraftInput(record: T): T { + const clone = cloneWithInternals( + record, + ({ key, descriptor }) => + typeof key === 'symbol' && descriptor.get == null, + ) + + for (const serializedPath of record[kRelationMap].keys()) { + const path = serializedPath.split('.') + definePropertyAtPath(clone, path, { + value: get(record, path), + writable: true, + enumerable: true, + configurable: true, + }) + } + + return clone + } + + /** + * Returns a reproducible collection ID number based on the collection + * creation order. Collection ID has to be reproducible across runtimes + * to enable synchronization. + */ + #generateCollectionId(): number { + collectionsCreated++ + const seed = 0 + const value = collectionsCreated.toString() + + let h1 = 0xdeadbeef ^ seed, + h2 = 0x41c6ce57 ^ seed + for (let i = 0, ch; i < value.length; i++) { + ch = value.charCodeAt(i) + h1 = Math.imul(h1 ^ ch, 2654435761) + h2 = Math.imul(h2 ^ ch, 1597334677) + } + h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) + h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909) + h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) + h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909) + + return 4294967296 * (2097151 & h2) + (h1 >>> 0) + } +} diff --git a/src/comparators/boolean.ts b/src/comparators/boolean.ts deleted file mode 100644 index e1afb636..00000000 --- a/src/comparators/boolean.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { BooleanQuery, QueryToComparator } from '../query/queryTypes' - -export const booleanComparators: QueryToComparator = { - equals(expected, actual) { - return actual === expected - }, - notEquals(expected, actual) { - return expected !== actual - }, -} diff --git a/src/comparators/date.ts b/src/comparators/date.ts deleted file mode 100644 index bdebe2ae..00000000 --- a/src/comparators/date.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { compareAsc as compareDates } from 'date-fns' -import { DateQuery, QueryToComparator } from '../query/queryTypes' - -export const dateComparators: QueryToComparator = { - equals(expected, actual) { - return compareDates(expected, actual) === 0 - }, - notEquals(expected, actual) { - return compareDates(expected, actual) !== 0 - }, - gt(expected, actual) { - return compareDates(actual, expected) === 1 - }, - gte(expected, actual) { - return [0, 1].includes(compareDates(actual, expected)) - }, - lt(expected, actual) { - return compareDates(actual, expected) === -1 - }, - lte(expected, actual) { - return [-1, 0].includes(compareDates(actual, expected)) - }, -} diff --git a/src/comparators/number.ts b/src/comparators/number.ts deleted file mode 100644 index f56a0594..00000000 --- a/src/comparators/number.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { NumberQuery, QueryToComparator } from '../query/queryTypes' -import { numberInRange } from '../utils/numberInRange' - -export const numberComparators: QueryToComparator = { - equals(expected, actual) { - return actual === expected - }, - notEquals(expected, actual) { - return !numberComparators.equals(expected, actual) - }, - between(expected, actual) { - return numberInRange(expected[0], expected[1], actual) - }, - notBetween(expected, actual) { - return !numberComparators.between(expected, actual) - }, - gt(expected, actual) { - return actual > expected - }, - gte(expected, actual) { - return actual >= expected - }, - lt(expected, actual) { - return actual < expected - }, - lte(expected, actual) { - return actual <= expected - }, - in(expected, actual) { - return expected.includes(actual) - }, - notIn(expected, actual) { - return !numberComparators.in(expected, actual) - }, -} diff --git a/src/comparators/string.ts b/src/comparators/string.ts deleted file mode 100644 index a44d2ac5..00000000 --- a/src/comparators/string.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { QueryToComparator, StringQuery } from '../query/queryTypes' - -export const stringComparators: QueryToComparator = { - equals(expected, actual) { - return expected === actual - }, - notEquals(expected, actual) { - return !stringComparators.equals(expected, actual) - }, - contains(expected, actual) { - return actual.includes(expected) - }, - notContains(expected, actual) { - return !stringComparators.contains(expected, actual) - }, - in(expected, actual) { - return expected.includes(actual) - }, - notIn(expected, actual) { - return !stringComparators.in(expected, actual) - }, -} diff --git a/src/db/Database.ts b/src/db/Database.ts deleted file mode 100644 index 92745e13..00000000 --- a/src/db/Database.ts +++ /dev/null @@ -1,177 +0,0 @@ -import md5 from 'md5' -import { invariant } from 'outvariant' -import { StrictEventEmitter } from 'strict-event-emitter' -import { - Entity, - ENTITY_TYPE, - KeyType, - ModelDictionary, - PrimaryKeyType, - PRIMARY_KEY, -} from '../glossary' - -export const SERIALIZED_INTERNAL_PROPERTIES_KEY = - 'SERIALIZED_INTERNAL_PROPERTIES' - -type Models = Record< - keyof Dictionary, - Map> -> - -export interface SerializedInternalEntityProperties { - entityType: string - primaryKey: PrimaryKeyType -} - -export interface SerializedEntity extends Entity { - [SERIALIZED_INTERNAL_PROPERTIES_KEY]: SerializedInternalEntityProperties -} - -export type DatabaseMethodToEventFn = ( - sourceId: string, - args: ArgsType, -) => void - -export interface DatabaseEventsMap { - create: DatabaseMethodToEventFn< - [ - modelName: KeyType, - entity: SerializedEntity, - customPrimaryKey?: PrimaryKeyType, - ] - > - update: DatabaseMethodToEventFn< - [ - modelName: KeyType, - prevEntity: SerializedEntity, - nextEntity: SerializedEntity, - ] - > - delete: DatabaseMethodToEventFn< - [modelName: KeyType, primaryKey: PrimaryKeyType] - > -} - -let callOrder = 0 - -export class Database { - public id: string - public events: StrictEventEmitter - private models: Models - - constructor(dictionary: Dictionary) { - this.events = new StrictEventEmitter() - this.models = Object.keys(dictionary).reduce>( - (acc, modelName: keyof Dictionary) => { - acc[modelName] = new Map>() - return acc - }, - {} as Models, - ) - - callOrder++ - this.id = this.generateId() - } - - /** - * Generates a unique MD5 hash based on the database - * module location and invocation order. Used to reproducibly - * identify a database instance among sibling instances. - */ - private generateId() { - const { stack } = new Error() - const callFrame = stack?.split('\n')[4] - const salt = `${callOrder}-${callFrame?.trim()}` - return md5(salt) - } - - private serializeEntity(entity: Entity): SerializedEntity { - return { - ...entity, - [SERIALIZED_INTERNAL_PROPERTIES_KEY]: { - entityType: entity[ENTITY_TYPE], - primaryKey: entity[PRIMARY_KEY], - }, - } - } - - getModel(name: ModelName) { - return this.models[name] - } - - create( - modelName: ModelName, - entity: Entity, - customPrimaryKey?: PrimaryKeyType, - ): Map> { - invariant( - entity[ENTITY_TYPE], - 'Failed to create a new "%s" record: provided entity has no type. %j', - modelName, - entity, - ) - invariant( - entity[PRIMARY_KEY], - 'Failed to create a new "%s" record: provided entity has no primary key. %j', - modelName, - entity, - ) - - const primaryKey = - customPrimaryKey || (entity[entity[PRIMARY_KEY]] as string) - - this.events.emit('create', this.id, [ - modelName, - this.serializeEntity(entity), - customPrimaryKey, - ]) - return this.getModel(modelName).set(primaryKey, entity) - } - - update( - modelName: ModelName, - prevEntity: Entity, - nextEntity: Entity, - ): void { - const prevPrimaryKey = prevEntity[prevEntity[PRIMARY_KEY]] as PrimaryKeyType - const nextPrimaryKey = nextEntity[prevEntity[PRIMARY_KEY]] as PrimaryKeyType - - if (nextPrimaryKey !== prevPrimaryKey) { - this.delete(modelName, prevPrimaryKey) - } - - this.getModel(modelName).set(nextPrimaryKey, nextEntity) - - // this.create(modelName, nextEntity, nextPrimaryKey) - this.events.emit('update', this.id, [ - modelName, - this.serializeEntity(prevEntity), - this.serializeEntity(nextEntity), - ]) - } - - delete( - modelName: ModelName, - primaryKey: PrimaryKeyType, - ): void { - this.getModel(modelName).delete(primaryKey) - this.events.emit('delete', this.id, [modelName, primaryKey]) - } - - has( - modelName: ModelName, - primaryKey: PrimaryKeyType, - ): boolean { - return this.getModel(modelName).has(primaryKey) - } - - count(modelName: ModelName) { - return this.getModel(modelName).size - } - - listEntities( - modelName: ModelName, - ): Entity[] { - return Array.from(this.getModel(modelName).values()) - } -} diff --git a/src/db/drop.ts b/src/db/drop.ts deleted file mode 100644 index 8f382b30..00000000 --- a/src/db/drop.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { FactoryAPI } from '../glossary' - -export function drop(factoryApi: FactoryAPI): void { - Object.values(factoryApi).forEach((model) => { - model.deleteMany({ where: {} }) - }) -} diff --git a/src/errors.ts b/src/errors.ts new file mode 100644 index 00000000..bce08cc4 --- /dev/null +++ b/src/errors.ts @@ -0,0 +1,56 @@ +import type { Collection } from '#/src/collection.js' +import type { PropertyPath } from '#/src/utils.js' +import type { RelationDeclarationOptions } from '#/src/relation.js' + +export enum OperationErrorCodes { + UNEXPECTED_ERROR = 'UNEXPECTED_ERROR', + INVALID_INITIAL_VALUES = 'INVALID_INITIAL_VALUES', + STRICT_QUERY_WITHOUT_RESULTS = 'STRICT_QUERY_WITHOUT_RESULTS', +} + +export class OperationError extends Error { + static for(code: OperationErrorCodes) { + return (message: string) => { + return new OperationError(message, code) + } + } + + constructor( + message: string, + public readonly code: OperationErrorCodes, + public readonly cause?: unknown, + ) { + super(message) + } +} + +export enum RelationErrorCodes { + RELATION_NOT_READY = 'RELATION_NOT_READY', + UNEXPECTED_SET_EXPRESSION = 'UNEXPECTED_SET_EXPRESSION', + INVALID_FOREIGN_RECORD = 'INVALID_FOREIGN_RECORD', + FORBIDDEN_UNIQUE_CREATE = 'FORBIDDEN_UNIQUE_CREATE', + FORBIDDEN_UNIQUE_UPDATE = 'FORBIDDEN_UNIQUE_UPDATE', +} + +export interface RelationErrorDetails { + path: PropertyPath + ownerCollection: Collection + foreignCollections: Array> + options: RelationDeclarationOptions +} + +export class RelationError extends Error { + static for(code: RelationErrorCodes, details: RelationErrorDetails) { + return (message: string) => { + return new RelationError(message, code, details) + } + } + + constructor( + message: string, + public readonly code: RelationErrorCodes, + public readonly details: RelationErrorDetails, + ) { + super(message) + } +} diff --git a/src/errors/OperationError.ts b/src/errors/OperationError.ts deleted file mode 100644 index 961379a0..00000000 --- a/src/errors/OperationError.ts +++ /dev/null @@ -1,15 +0,0 @@ -export enum OperationErrorType { - MissingPrimaryKey = 'MissingPrimaryKey', - DuplicatePrimaryKey = 'DuplicatePrimaryKey', - EntityNotFound = 'EntityNotFound', -} - -export class OperationError extends Error { - public type: ErrorType - - constructor(type: ErrorType, message?: string) { - super(message) - this.name = 'OperationError' - this.type = type - } -} diff --git a/src/extensions/index.ts b/src/extensions/index.ts new file mode 100644 index 00000000..13dd2278 --- /dev/null +++ b/src/extensions/index.ts @@ -0,0 +1,21 @@ +import type { Collection } from '#/src/collection.js' + +export interface Extension { + /** + * Name of your extension. + */ + name: string + + /** + * A function that is called when your extension is applied by a collection. + * @param collection Reference to the `Collection` instance that applies the extension. + */ + extend: (collection: Collection) => void +} + +/** + * Creates a new extension. + */ +export function defineExtension(options: Extension): Extension { + return options +} diff --git a/src/extensions/persist.ts b/src/extensions/persist.ts new file mode 100644 index 00000000..3380c312 --- /dev/null +++ b/src/extensions/persist.ts @@ -0,0 +1,182 @@ +import { invariant } from 'outvariant' +import { unset } from 'es-toolkit/compat' +import { defineExtension } from '#/src/extensions/index.js' +import { + kCollectionId, + kPrimaryKey, + kRelationMap, + type Collection, + type RecordType, +} from '#/src/collection.js' +import { Logger } from '#/src/logger.js' +import type { PropertyPath } from '#/src/utils.js' + +const STORAGE_KEY = 'msw/data/storage' +const METADATA_KEY = '__metadata__' + +interface SerializedCollection { + version: number + collectionId: number + records: Array +} + +export interface SerializedRecord { + [key: string]: unknown + [METADATA_KEY]: RecordMetadata +} + +interface RecordMetadata { + primaryKey: string + relations: Array<{ + path: PropertyPath + foreignKeys: Array + }> +} + +/** + * Persists the collection between page reloads. + */ +export function persist() { + return defineExtension({ + name: 'persist', + async extend(collection) { + if ( + typeof window === 'undefined' || + typeof localStorage === 'undefined' + ) { + return + } + + const logger = new Logger('extension').extend('persist') + const COLLECTION_KEY = `${STORAGE_KEY}/${collection[kCollectionId]}` + + // Flush the collection's on page unload. + window.addEventListener('unload', () => { + localStorage.setItem( + COLLECTION_KEY, + /** + * @fixme Stringifying relations errors because they produce + * circular structures. Relations have to be stripped out of the records. + * Maybe preserved in the metadata? + */ + JSON.stringify({ + version: 1, + collectionId: collection[kCollectionId], + records: collection.all().map(serializeRecord), + } satisfies SerializedCollection), + ) + }) + + const rawPersistedData = localStorage.getItem(COLLECTION_KEY) + + if (!rawPersistedData) { + return + } + const persistedData = JSON.parse(rawPersistedData) as SerializedCollection + + invariant( + persistedData.collectionId === collection[kCollectionId], + 'Failed to hydrate data for collection "%s": parsed a state of an unknown collection "%s"', + collection[kCollectionId], + persistedData.collectionId, + ) + + logger.log(`found (${persistedData.records.length}) records to hydrate!`) + + await Promise.all( + persistedData.records.map(async (serializedRecord) => { + logger.log('hydrating record...', { serializedRecord }) + await createFromSerializedRecord(collection, serializedRecord) + }), + ) + + logger.log('hydration done!', collection.all()) + }, + }) +} + +export function serializeRecord(record: RecordType): SerializedRecord { + const result = structuredClone(record) as any as SerializedRecord + + const metadata: RecordMetadata = { + primaryKey: record[kPrimaryKey], + relations: [], + } + + // Delete relational keys since they can produce non-serializable structures. + const relations = record[kRelationMap] + for (const [path, relation] of relations) { + metadata.relations.push({ + path: relation.path, + foreignKeys: Array.from(relation.foreignKeys), + }) + + unset(result, path) + } + + result[METADATA_KEY] = metadata + + return result +} + +export function deserializeRecord( + record: SerializedRecord, +): Record { + const metadata = record[METADATA_KEY] + + invariant( + metadata, + 'Failed to deserialize record (%j): metadata is missing', + record, + ) + + // Restore the primary key for this record so it's preserved across reloads. + Object.defineProperties(record, { + [kPrimaryKey]: { + enumerable: false, + configurable: false, + value: metadata.primaryKey, + }, + }) + + delete record[METADATA_KEY as keyof typeof record] + + invariant( + !(METADATA_KEY in record), + 'Failed to deserialize record (%j): metadata not cleared', + record, + ) + + return record +} + +export async function createFromSerializedRecord( + collection: Collection, + serializedRecord: SerializedRecord, +): Promise { + const metadata = serializedRecord[METADATA_KEY] + const initialValues = deserializeRecord(serializedRecord) + + invariant( + !(METADATA_KEY in initialValues), + 'Failed to create record from deserialized record (%j): metadata not cleared', + initialValues, + ) + + const record: RecordType = await collection.create(initialValues) + const relationMap = record[kRelationMap] + + for (const serializedRelation of metadata.relations) { + const relation = relationMap.get(serializedRelation.path.join('.')) + + if (relation == null) { + continue + } + + for (const foreignKey of serializedRelation.foreignKeys) { + relation.foreignKeys.add(foreignKey) + } + } + + return record +} diff --git a/src/extensions/sync.ts b/src/extensions/sync.ts index 90bb4576..44a0420e 100644 --- a/src/extensions/sync.ts +++ b/src/extensions/sync.ts @@ -1,134 +1,232 @@ -import { ENTITY_TYPE, PRIMARY_KEY, Entity } from '../glossary' +import { set } from 'es-toolkit/compat' +import { defineExtension } from '#/src/extensions/index.js' import { - Database, - DatabaseEventsMap, - SerializedEntity, - SERIALIZED_INTERNAL_PROPERTIES_KEY, -} from '../db/Database' -import { inheritInternalProperties } from '../utils/inheritInternalProperties' - -export type DatabaseMessageEventData = + kCollectionId, + kPrimaryKey, + kRelationMap, + type Collection, + type RecordType, +} from '#/src/collection.js' +import type { Query } from '#/src/query.js' +import { isObject, type PropertyPath } from '#/src/utils.js' +import { Logger } from '#/src/logger.js' +import { + serializeRecord, + createFromSerializedRecord, + type SerializedRecord, +} from '#/src/extensions/persist.js' + +type BroadcastOperation = | { - operationType: 'create' - payload: Parameters + type: 'create' + senderId: Collection[typeof kCollectionId] + record: SerializedRecord } | { - operationType: 'update' - payload: Parameters + type: 'update' + senderId: Collection[typeof kCollectionId] + primaryKey: string + path: PropertyPath + nextValue: unknown } | { - operationType: 'delete' - payload: Parameters + type: 'delete' + senderId: Collection[typeof kCollectionId] + primaryKey: string } -function removeListeners( - event: Event, - db: Database, -) { - const listeners = db.events.listeners(event) as DatabaseEventsMap[Event][] - - listeners.forEach((listener) => { - db.events.removeListener(event, listener) - }) - - return () => { - listeners.forEach((listener) => { - db.events.addListener(event, listener) - }) - } -} +const BROADCAST_CHANNEL_NAME = 'msw/data/sync' /** - * Sets the serialized internal properties as symbols - * on the given entity. - * @note `Symbol` properties are stripped off when sending - * an object over an event emitter. + * Synchronizes collection operations (create/update/delete) + * with the same collection in another browser tab(s). */ -function deserializeEntity(entity: SerializedEntity): Entity { - const { - [SERIALIZED_INTERNAL_PROPERTIES_KEY]: internalProperties, - ...publicProperties - } = entity - - inheritInternalProperties(publicProperties, { - [ENTITY_TYPE]: internalProperties.entityType, - [PRIMARY_KEY]: internalProperties.primaryKey, - }) - - return publicProperties -} - -/** - * Synchronizes database operations across multiple clients. - */ -export function sync(db: Database) { - const IS_BROWSER = typeof window !== 'undefined' - const SUPPORTS_BROADCAST_CHANNEL = typeof BroadcastChannel !== 'undefined' - - if (!IS_BROWSER || !SUPPORTS_BROADCAST_CHANNEL) { - return - } +export function sync() { + const logger = new Logger('extension').extend('sync') + + return defineExtension({ + name: 'sync', + extend(collection) { + if ( + typeof window === 'undefined' || + typeof BroadcastChannel === 'undefined' + ) { + return + } - const channel = new BroadcastChannel('mswjs/data/sync') + const channel = new BroadcastChannel(BROADCAST_CHANNEL_NAME) + const hookContext = { skip: false } - channel.addEventListener( - 'message', - (event: MessageEvent) => { - const [sourceId] = event.data.payload + logger.log('applying extension...', { channel }) - // Ignore messages originating from unrelated databases. - // Useful in case of multiple databases on the same page. - if (db.id !== sourceId) { - return + const performWithoutBroadcasting = async ( + callback: () => Promise, + ): Promise => { + try { + hookContext.skip = true + await callback() + } finally { + hookContext.skip = false + } } - // Remove database event listener for the signaled operation - // to prevent an infinite loop when applying this operation. - const restoreListeners = removeListeners(event.data.operationType, db) - - // Apply the database operation signaled from another client - // to the current database instance. - switch (event.data.operationType) { - case 'create': { - const [modelName, entity, customPrimaryKey] = event.data.payload[1] - db.create(modelName, deserializeEntity(entity), customPrimaryKey) - break + channel.onmessage = async (event: MessageEvent) => { + const { data } = event + + if (!isObject(data)) { + return } - case 'update': { - const [modelName, prevEntity, nextEntity] = event.data.payload[1] - db.update( - modelName, - deserializeEntity(prevEntity), - deserializeEntity(nextEntity), + logger.log( + `sync event from another collection (${event.data.type})`, + event, + ) + + // Ignore sync events from irrelevant collections. + // This only works because collection ID is based on the call order + // and remains reproducible across runtimes. + if (data.senderId !== collection[kCollectionId]) { + logger.log( + `sender id (${data.senderId}) differs from this collection id (${collection[kCollectionId]}), skipping...`, ) - break + return } - default: { - db[event.data.operationType](...event.data.payload[1]) + switch (data.type) { + case 'create': { + logger.warn('creating new record...', data) + + /** + * @note Use the `.create()` method to correctly evolve the schema. + * This way, non-serializable schemas can survive sync as long as + * the initial values are serializable. + */ + await performWithoutBroadcasting(async () => { + const record = await createFromSerializedRecord( + collection, + data.record, + ) + + /** + * @note Extraneous records might not have been associated with their owners + * at the time of sync. Manually ensure the owner is referenced in those relations. + */ + record[kRelationMap].forEach((relation) => { + relation.foreignCollections.forEach((foreignCollection) => { + const foreignRecords = foreignCollection.findMany((q) => + q.where((foreignRecord) => { + return relation.foreignKeys.has( + foreignRecord[kPrimaryKey], + ) + }), + ) + + const foreignRelations = foreignRecords.flatMap( + (foreignRecord) => { + return relation.getRelationsToOwner(foreignRecord) + }, + ) + foreignRelations.forEach((foreignRelation) => { + foreignRelation.foreignKeys.add(record[kPrimaryKey]) + }) + }) + }) + }) + break + } + + case 'update': { + logger.log('updating record...') + + await performWithoutBroadcasting(async () => { + await collection.update( + (q: Query) => + q.where((record: RecordType) => { + return record[kPrimaryKey] === data.primaryKey + }), + { + data(record) { + set(record, data.path, data.nextValue) + }, + }, + ) + }) + break + } + + case 'delete': { + logger.log('deleting record...') + + await performWithoutBroadcasting(async () => { + collection.delete((q: Query) => + q.where((record: RecordType) => { + return record[kPrimaryKey] === data.primaryKey + }), + ) + }) + break + } + + default: { + // @ts-expect-error Runtime validation. + throw new Error(`Unknown sync event type "${data.type}"`) + } } } - // Re-attach database event listeners. - restoreListeners() - }, - ) - - // Broadcast the emitted event from this client - // to all the other connected clients. - function broadcastDatabaseEvent( - operationType: Event, - ) { - return (...payload: Parameters) => { - channel.postMessage({ - operationType, - payload, - } as DatabaseMessageEventData) - } - } + channel.onmessageerror = (event) => { + logger.log('sync channel error!', event) + } + + const broadcastOperation = (operation: BroadcastOperation): void => { + logger.log('broadcasting...', operation) + channel.postMessage(operation) + } - db.events.on('create', broadcastDatabaseEvent('create')) - db.events.on('update', broadcastDatabaseEvent('update')) - db.events.on('delete', broadcastDatabaseEvent('delete')) + collection.hooks.on('create', (event) => { + const { record, initialValues } = event.data + + logger.warn( + 'record created, should broadcast?', + { record, initialValues }, + !hookContext.skip, + ) + + if (!hookContext.skip) { + broadcastOperation({ + type: 'create', + senderId: collection[kCollectionId], + record: serializeRecord(record), + }) + } + }) + + collection.hooks.on('update', (event) => { + const { prevRecord, path, nextValue } = event.data + logger.log('record updated, should broadcast?', !hookContext.skip) + + if (!hookContext.skip) { + broadcastOperation({ + type: 'update', + senderId: collection[kCollectionId], + primaryKey: prevRecord[kPrimaryKey], + path, + nextValue, + }) + } + }) + + collection.hooks.on('delete', (event) => { + logger.log('record deleted, should broadcast?', !hookContext.skip) + + if (!hookContext.skip) { + broadcastOperation({ + type: 'delete', + senderId: collection[kCollectionId], + primaryKey: event.data.deletedRecord[kPrimaryKey], + }) + } + }) + }, + }) } diff --git a/src/factory.ts b/src/factory.ts deleted file mode 100644 index 96c368ae..00000000 --- a/src/factory.ts +++ /dev/null @@ -1,295 +0,0 @@ -import { format } from 'outvariant' -import { - DATABASE_INSTANCE, - Entity, - FactoryAPI, - ModelAPI, - ModelDefinition, - ModelDictionary, - PRIMARY_KEY, -} from './glossary' -import { first } from './utils/first' -import { executeQuery } from './query/executeQuery' -import { parseModelDefinition } from './model/parseModelDefinition' -import { createModel } from './model/createModel' -import { updateEntity } from './model/updateEntity' -import { OperationError, OperationErrorType } from './errors/OperationError' -import { Database } from './db/Database' -import { generateRestHandlers } from './model/generateRestHandlers' -import { - generateGraphQLHandlers, - generateGraphQLSchema, -} from './model/generateGraphQLHandlers' -import { sync } from './extensions/sync' - -/** - * Create a database with the given models. - */ -export function factory( - dictionary: Dictionary, -): FactoryAPI { - const db = new Database(dictionary) - - // Initialize database extensions. - sync(db) - - return Object.entries(dictionary).reduce( - (acc, [modelName, props]) => { - acc[modelName] = createModelApi( - dictionary, - modelName, - props, - db, - ) - return acc - }, - { - [DATABASE_INSTANCE]: db, - }, - ) -} - -function createModelApi< - Dictionary extends ModelDictionary, - ModelName extends string, ->( - dictionary: Dictionary, - modelName: ModelName, - definition: ModelDefinition, - db: Database, -) { - const parsedModel = parseModelDefinition(dictionary, modelName, definition) - const { primaryKey } = parsedModel - - const api: ModelAPI = { - create(initialValues = {}) { - const entity = createModel( - modelName, - definition, - dictionary, - parsedModel, - initialValues, - db, - ) - - const entityId = entity[entity[PRIMARY_KEY]] as string - - if (!entityId) { - throw new OperationError( - OperationErrorType.MissingPrimaryKey, - format( - 'Failed to create a "%s" entity: expected the primary key "%s" to have a value, but got: %s', - modelName, - primaryKey, - entityId, - ), - ) - } - - // Prevent creation of multiple entities with the same primary key value. - if (db.has(modelName, entityId)) { - throw new OperationError( - OperationErrorType.DuplicatePrimaryKey, - format( - 'Failed to create a "%s" entity: an entity with the same primary key "%s" ("%s") already exists.', - modelName, - entityId, - entity[PRIMARY_KEY], - ), - ) - } - - db.create(modelName, entity) - return entity - }, - count(query) { - if (!query) { - return db.count(modelName) - } - - const results = executeQuery(modelName, primaryKey, query, db) - return results.length - }, - findFirst(query) { - const results = executeQuery(modelName, primaryKey, query, db) - const firstResult = first(results) - - if (query.strict && firstResult == null) { - throw new OperationError( - OperationErrorType.EntityNotFound, - format( - 'Failed to execute "findFirst" on the "%s" model: no entity found matching the query "%j".', - modelName, - query.where, - ), - ) - } - - return firstResult as Entity - }, - findMany(query) { - const results = executeQuery(modelName, primaryKey, query, db) - - if (results.length === 0 && query.strict) { - throw new OperationError( - OperationErrorType.EntityNotFound, - format( - 'Failed to execute "findMany" on the "%s" model: no entities found matching the query "%j".', - modelName, - query.where, - ), - ) - } - - return results - }, - getAll() { - return db.listEntities(modelName) - }, - update({ strict, ...query }) { - const results = executeQuery(modelName, primaryKey, query, db) - const prevRecord = first(results) - - if (!prevRecord) { - if (strict) { - throw new OperationError( - OperationErrorType.EntityNotFound, - format( - 'Failed to execute "update" on the "%s" model: no entity found matching the query "%j".', - modelName, - query.where, - ), - ) - } - - return null as any - } - - const nextRecord = updateEntity(prevRecord, query.data, definition) - - if ( - nextRecord[prevRecord[PRIMARY_KEY]] !== - prevRecord[prevRecord[PRIMARY_KEY]] - ) { - if (db.has(modelName, nextRecord[prevRecord[PRIMARY_KEY]])) { - throw new OperationError( - OperationErrorType.DuplicatePrimaryKey, - format( - 'Failed to execute "update" on the "%s" model: the entity with a primary key "%s" ("%s") already exists.', - modelName, - nextRecord[prevRecord[PRIMARY_KEY]], - primaryKey, - ), - ) - } - } - - db.update(modelName, prevRecord, nextRecord) - - return nextRecord - }, - updateMany({ strict, ...query }) { - const records = executeQuery(modelName, primaryKey, query, db) - const updatedRecords: Entity[] = [] - - if (records.length === 0) { - if (strict) { - throw new OperationError( - OperationErrorType.EntityNotFound, - format( - 'Failed to execute "updateMany" on the "%s" model: no entities found matching the query "%j".', - modelName, - query.where, - ), - ) - } - - return null as any - } - - records.forEach((prevRecord) => { - const nextRecord = updateEntity(prevRecord, query.data, definition) - - if ( - nextRecord[prevRecord[PRIMARY_KEY]] !== - prevRecord[prevRecord[PRIMARY_KEY]] - ) { - if (db.has(modelName, nextRecord[prevRecord[PRIMARY_KEY]])) { - throw new OperationError( - OperationErrorType.DuplicatePrimaryKey, - format( - 'Failed to execute "updateMany" on the "%s" model: the entity with a primary key "%s" ("%s") already exists.', - modelName, - nextRecord[prevRecord[PRIMARY_KEY]], - primaryKey, - ), - ) - } - } - - db.update(modelName, prevRecord, nextRecord) - updatedRecords.push(nextRecord) - }) - - return updatedRecords - }, - delete({ strict, ...query }) { - const results = executeQuery(modelName, primaryKey, query, db) - const record = first(results) - - if (!record) { - if (strict) { - throw new OperationError( - OperationErrorType.EntityNotFound, - format( - 'Failed to execute "delete" on the "%s" model: no entity found matching the query "%o".', - modelName, - query.where, - ), - ) - } - - return null as any - } - - db.delete(modelName, record[record[PRIMARY_KEY]] as string) - return record - }, - deleteMany({ strict, ...query }) { - const records = executeQuery(modelName, primaryKey, query, db) - - if (records.length === 0) { - if (strict) { - throw new OperationError( - OperationErrorType.EntityNotFound, - format( - 'Failed to execute "deleteMany" on the "%s" model: no entities found matching the query "%o".', - modelName, - query.where, - ), - ) - } - - return null as any - } - - records.forEach((record) => { - db.delete(modelName, record[record[PRIMARY_KEY]] as string) - }) - - return records - }, - toHandlers(type: 'rest' | 'graphql', baseUrl: string): any { - if (type === 'graphql') { - return generateGraphQLHandlers(modelName, definition, api, baseUrl) - } - - return generateRestHandlers(modelName, definition, api, baseUrl) - }, - toGraphQLSchema() { - return generateGraphQLSchema(modelName, definition, api) - }, - } - - return api -} diff --git a/src/glossary.ts b/src/glossary.ts deleted file mode 100644 index 63326aba..00000000 --- a/src/glossary.ts +++ /dev/null @@ -1,244 +0,0 @@ -import { GraphQLSchema } from 'graphql' -import { GraphQLHandler, RestHandler } from 'msw' -import { Database } from './db/Database' -import { NullableProperty } from './nullable' -import { PrimaryKey } from './primaryKey' -import { - BulkQueryOptions, - QueryOptions, - QuerySelector, - WeakQuerySelector, -} from './query/queryTypes' -import { OneOf, ManyOf } from './relations/Relation' - -export const PRIMARY_KEY = Symbol('primaryKey') -export const ENTITY_TYPE = Symbol('type') -export const DATABASE_INSTANCE = Symbol('databaseInstance') - -export type KeyType = string | number | symbol -export type AnyObject = Record -export type PrimaryKeyType = string | number -export type PrimitiveValueType = string | number | boolean | Date -export type ModelValueType = PrimitiveValueType | PrimitiveValueType[] -export type ModelValueTypeGetter = () => ModelValueType - -export type ModelDefinition = Record - -export type ModelDefinitionValue = - | PrimaryKey - | ModelValueTypeGetter - | NullableProperty - | OneOf - | ManyOf - | NestedModelDefinition - -export type NestedModelDefinition = { - [propertyName: string]: - | ModelValueTypeGetter - | NullableProperty - | OneOf - | ManyOf - | NestedModelDefinition -} - -export type FactoryAPI> = { - [ModelName in keyof Dictionary]: ModelAPI -} & { - [DATABASE_INSTANCE]: Database -} - -export type ModelDictionary = Record> - -export type Limit = { - [Key in keyof Definition]: Definition[Key] extends ModelDefinitionValue - ? Definition[Key] - : { - error: 'expected primary key, initial value, or relation' - } -} - -export interface InternalEntityProperties { - readonly [ENTITY_TYPE]: ModelName - readonly [PRIMARY_KEY]: PrimaryKeyType -} - -export type Entity< - Dictionary extends ModelDictionary, - ModelName extends keyof Dictionary, -> = PublicEntity & InternalEntityProperties - -export type PublicEntity< - Dictionary extends ModelDictionary, - ModelName extends keyof Dictionary, -> = Value - -export type RequiredExactlyOne< - ObjectType, - KeysType extends keyof ObjectType = keyof ObjectType, -> = { - [Key in KeysType]: Required> & - Partial, never>> -}[KeysType] & - Pick> - -export type DeepRequiredExactlyOne = - RequiredExactlyOne<{ - [Key in keyof Target]: Target[Key] extends AnyObject - ? DeepRequiredExactlyOne - : Target[Key] - }> - -export type InitialValues< - Dictionary extends ModelDictionary, - ModelName extends keyof Dictionary, -> = Partial> - -export type StrictQueryReturnType< - Options extends QueryOptions, - ValueType extends unknown, -> = Options['strict'] extends true ? ValueType : ValueType | null - -export interface ModelAPI< - Dictionary extends ModelDictionary, - ModelName extends keyof Dictionary, -> { - /** - * Create a single entity for the model. - */ - create( - initialValues?: InitialValues, - ): Entity - /** - * Return the total number of entities. - */ - count( - query?: QueryOptions & QuerySelector>, - ): number - /** - * Find a first entity matching the query. - */ - findFirst( - query: Options & QuerySelector>, - ): StrictQueryReturnType> - /** - * Find multiple entities. - */ - findMany( - query: QueryOptions & - WeakQuerySelector> & - BulkQueryOptions>, - ): Entity[] - /** - * Return all entities of the current model. - */ - getAll(): Entity[] - /** - * Update a single entity with the next data. - */ - update( - query: Options & - QuerySelector> & { - data: Partial> - }, - ): StrictQueryReturnType> - /** - * Update many entities with the next data. - */ - updateMany( - query: Options & - QuerySelector> & { - data: Partial> - }, - ): StrictQueryReturnType[]> - /** - * Delete a single entity. - */ - delete( - query: Options & QuerySelector>, - ): StrictQueryReturnType> - /** - * Delete multiple entities. - */ - deleteMany( - query: Options & QuerySelector>, - ): StrictQueryReturnType[]> - /** - * Generate request handlers of the given type based on the model definition. - */ - toHandlers(type: 'rest', baseUrl?: string): RestHandler[] - /** - * Generate request handlers of the given type based on the model definition. - */ - toHandlers(type: 'graphql', baseUrl?: string): GraphQLHandler[] - - /** - * Generate a graphql schema based on the model definition. - */ - toGraphQLSchema(): GraphQLSchema -} - -export type UpdateManyValue< - Target extends AnyObject, - Dictionary extends ModelDictionary, - ModelRoot extends AnyObject = Target, -> = - | Value - | { - [Key in keyof Target]?: Target[Key] extends PrimaryKey - ? ( - prevValue: ReturnType, - entity: Value, - ) => ReturnType - : Target[Key] extends ModelValueTypeGetter - ? ( - prevValue: ReturnType, - entity: Value, - ) => ReturnType - : Target[Key] extends OneOf - ? ( - prevValue: PublicEntity, - entity: Value, - ) => PublicEntity - : Target[Key] extends ManyOf - ? ( - prevValue: PublicEntity[], - entity: Value, - ) => PublicEntity[] - : Target[Key] extends AnyObject - ? Partial> - : ( - prevValue: ReturnType, - entity: Value, - ) => ReturnType - } - -export type Value< - Target extends AnyObject, - Dictionary extends ModelDictionary, -> = { - [Key in keyof Target]: Target[Key] extends PrimaryKey - ? ReturnType - : // Extract underlying value type of nullable properties - Target[Key] extends NullableProperty - ? ReturnType - : // Extract value type from OneOf relations. - Target[Key] extends OneOf - ? Nullable extends true - ? PublicEntity | null - : PublicEntity | undefined - : // Extract value type from ManyOf relations. - Target[Key] extends ManyOf - ? Nullable extends true - ? PublicEntity[] | null - : PublicEntity[] - : // Account for primitive value getters because - // native constructors (i.e. StringConstructor) satisfy - // the "AnyObject" predicate below. - Target[Key] extends ModelValueTypeGetter - ? ReturnType - : // Handle nested objects. - Target[Key] extends AnyObject - ? Partial> - : // Otherwise, return the return type of primitive value getters. - ReturnType -} diff --git a/src/hooks.ts b/src/hooks.ts new file mode 100644 index 00000000..82f5793b --- /dev/null +++ b/src/hooks.ts @@ -0,0 +1,34 @@ +import type { StandardSchemaV1 } from '@standard-schema/spec' +import { Emitter, TypedEvent } from 'rettime' +import type { Collection, RecordType } from '#/src/collection.js' + +export type HookEventMap = { + create: TypedEvent<{ + record: RecordType> + initialValues?: StandardSchemaV1.InferInput + }> + update: TypedEvent<{ + prevRecord: RecordType> + nextRecord: RecordType> + path: Array + prevValue: unknown + nextValue: unknown + }> + delete: TypedEvent<{ + deletedRecord: RecordType> + }> +} + +export type HookEventListener< + T extends Collection, + Hook extends keyof HookEventMap, + Schema extends StandardSchemaV1 = T extends Collection + ? Schema + : any, +> = Emitter.Listener + +export function createHooksEmitter() { + const emitter = new Emitter>() + + return emitter +} diff --git a/src/index.ts b/src/index.ts index 08a43593..6ebdbb20 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,10 @@ -export { factory } from './factory' -export { primaryKey } from './primaryKey' -export { nullable } from './nullable' -export { oneOf } from './relations/oneOf' -export { manyOf } from './relations/manyOf' -export { drop } from './db/drop' -export { identity } from './utils/identity' - -/* Types */ -export { PRIMARY_KEY, ENTITY_TYPE } from './glossary' +export { Collection, type CollectionOptions } from './collection.js' +export { Query, type Condition, type PredicateFunction } from './query.js' +export { Relation, type RelationDeclarationOptions } from './relation.js' +export type { HookEventMap, HookEventListener } from './hooks.js' +export { + OperationError, + RelationError, + RelationErrorCodes, + type RelationErrorDetails, +} from './errors.js' diff --git a/src/logger.ts b/src/logger.ts new file mode 100644 index 00000000..e0ba895a --- /dev/null +++ b/src/logger.ts @@ -0,0 +1,27 @@ +const IS_LOGGING_ENABLED = false + +export class Logger { + constructor(private readonly domain: string) {} + + public log(...args: Array) { + if (IS_LOGGING_ENABLED) { + console.log(`[${this.domain}]`, ...args) + } + } + + public warn(...args: Array) { + if (IS_LOGGING_ENABLED) { + console.warn(`[${this.domain}]`, ...args) + } + } + + public trace(...args: Array) { + if (IS_LOGGING_ENABLED) { + console.trace(`[${this.domain}]`, ...args) + } + } + + public extend(subdomain: string | number) { + return new Logger(`${this.domain}] [${subdomain}`) + } +} diff --git a/src/model/createModel.ts b/src/model/createModel.ts deleted file mode 100644 index 67d8e677..00000000 --- a/src/model/createModel.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { debug } from 'debug' -import { invariant } from 'outvariant' -import get from 'lodash/get' -import set from 'lodash/set' -import isFunction from 'lodash/isFunction' -import { Database } from '../db/Database' -import { - ENTITY_TYPE, - Entity, - InternalEntityProperties, - ModelDefinition, - ModelDictionary, - PRIMARY_KEY, - Value, -} from '../glossary' -import { ParsedModelDefinition } from './parseModelDefinition' -import { defineRelationalProperties } from './defineRelationalProperties' -import { PrimaryKey } from '../primaryKey' -import { Relation } from '../relations/Relation' -import { NullableProperty } from '../nullable' -import { isModelValueType } from '../utils/isModelValueType' - -const log = debug('createModel') - -export function createModel< - Dictionary extends ModelDictionary, - ModelName extends string, ->( - modelName: ModelName, - definition: ModelDefinition, - dictionary: Dictionary, - parsedModel: ParsedModelDefinition, - initialValues: Partial>, - db: Database, -): Entity { - const { primaryKey, properties, relations } = parsedModel - - log( - `creating a "${modelName}" entity (primary key: "${primaryKey}")`, - definition, - parsedModel, - relations, - initialValues, - ) - - const internalProperties: InternalEntityProperties = { - [ENTITY_TYPE]: modelName, - [PRIMARY_KEY]: primaryKey, - } - - const publicProperties = properties.reduce>( - (properties, propertyName) => { - const initialValue = get(initialValues, propertyName) - const propertyDefinition = get(definition, propertyName) - - // Ignore relational properties at this stage. - if (propertyDefinition instanceof Relation) { - return properties - } - - if (propertyDefinition instanceof PrimaryKey) { - set( - properties, - propertyName, - initialValue || propertyDefinition.getPrimaryKeyValue(), - ) - return properties - } - - if (propertyDefinition instanceof NullableProperty) { - const value = - initialValue === null || isModelValueType(initialValue) - ? initialValue - : propertyDefinition.getValue() - - set(properties, propertyName, value) - return properties - } - - invariant( - initialValue !== null, - 'Failed to create a "%s" entity: a non-nullable property "%s" cannot be instantiated with null. Use the "nullable" function when defining this property to support nullable value.', - modelName, - propertyName.join('.'), - ) - - if (isModelValueType(initialValue)) { - log( - '"%s" has a plain initial value:', - `${modelName}.${propertyName}`, - initialValue, - ) - set(properties, propertyName, initialValue) - return properties - } - - if (isFunction(propertyDefinition)) { - set(properties, propertyName, propertyDefinition()) - return properties - } - - return properties - }, - {}, - ) - - const entity = Object.assign( - {}, - publicProperties, - internalProperties, - ) as Entity - - defineRelationalProperties(entity, initialValues, relations, dictionary, db) - - log('created "%s" entity:', modelName, entity) - - return entity -} diff --git a/src/model/defineRelationalProperties.ts b/src/model/defineRelationalProperties.ts deleted file mode 100644 index f8893f88..00000000 --- a/src/model/defineRelationalProperties.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { debug } from 'debug' -import get from 'lodash/get' -import { invariant } from 'outvariant' -import { Database } from '../db/Database' -import { - Entity, - ENTITY_TYPE, - ModelDictionary, - PRIMARY_KEY, - Value, -} from '../glossary' -import { RelationKind, RelationsList } from '../relations/Relation' - -const log = debug('defineRelationalProperties') - -export function defineRelationalProperties( - entity: Entity, - initialValues: Partial>, - relations: RelationsList, - dictionary: ModelDictionary, - db: Database, -): void { - log('defining relational properties...', { entity, initialValues, relations }) - - for (const { propertyPath, relation } of relations) { - invariant( - dictionary[relation.target.modelName], - 'Failed to define a "%s" relational property to "%s" on "%s": cannot find a model by the name "%s".', - relation.kind, - propertyPath.join('.'), - entity[ENTITY_TYPE], - relation.target.modelName, - ) - - const references: Value | null | undefined = get( - initialValues, - propertyPath, - ) - - invariant( - references !== null || relation.attributes.nullable, - 'Failed to define a "%s" relationship to "%s" at "%s.%s" (%s: "%s"): cannot set a non-nullable relationship to null.', - - relation.kind, - relation.target.modelName, - entity[ENTITY_TYPE], - propertyPath.join('.'), - entity[PRIMARY_KEY], - entity[entity[PRIMARY_KEY]], - ) - - log( - `setting relational property "${entity[ENTITY_TYPE]}.${propertyPath.join( - '.', - )}" with references: %j`, - references, - relation, - ) - - relation.apply(entity, propertyPath, dictionary, db) - - if (references) { - log('has references, applying a getter...') - relation.resolveWith(entity, references) - continue - } - - if (relation.attributes.nullable) { - log('has no references but is nullable, applying a getter to null...') - relation.resolveWith(entity, null) - continue - } - - if (relation.kind === RelationKind.ManyOf) { - log( - 'has no references but is a non-nullable "manyOf" relationship, applying a getter to []...', - ) - relation.resolveWith(entity, []) - continue - } - - log('has no relations, skipping the getter...') - } -} diff --git a/src/model/generateGraphQLHandlers.ts b/src/model/generateGraphQLHandlers.ts deleted file mode 100644 index 45fd0511..00000000 --- a/src/model/generateGraphQLHandlers.ts +++ /dev/null @@ -1,301 +0,0 @@ -import pluralize from 'pluralize' -import { - graphql as executeGraphQL, - GraphQLObjectType, - GraphQLID, - GraphQLInt, - GraphQLString, - GraphQLList, - GraphQLSchema, - GraphQLFieldConfigMap, - GraphQLInputObjectType, - GraphQLInputFieldConfigMap, - GraphQLBoolean, - GraphQLInputType, - GraphQLScalarType, - GraphQLFieldConfigArgumentMap, -} from 'graphql' -import { GraphQLHandler, graphql } from 'msw' -import { ModelAPI, ModelDefinition, ModelDictionary } from '../glossary' -import { PrimaryKey } from '../primaryKey' -import { capitalize } from '../utils/capitalize' -import { QueryToComparator } from '../query/queryTypes' -import { booleanComparators } from '../comparators/boolean' -import { stringComparators } from '../comparators/string' -import { numberComparators } from '../comparators/number' - -interface GraphQLFieldsMap { - fields: GraphQLFieldConfigMap - inputFields: GraphQLInputFieldConfigMap - queryInputFields: GraphQLInputFieldConfigMap -} - -/** - * Derive a GraphQL scalar type from a variable. - */ -export function getGraphQLType(value: any) { - const resolvedValue = typeof value === 'function' ? value() : value - switch (resolvedValue.constructor.name) { - case 'Number': - return GraphQLInt - case 'Boolean': - return GraphQLBoolean - default: - return GraphQLString - } -} - -/** - * Create a GraphQLInputObjectType from a given comparator function. - */ -function createComparatorGraphQLInputType( - name: string, - comparators: QueryToComparator, - type: GraphQLInputType, -) { - return new GraphQLInputObjectType({ - name, - fields: Object.keys(comparators).reduce( - (fields, comparatorFn) => { - const fieldType = ['between', 'notBetween', 'in', 'notIn'].includes(comparatorFn) ? GraphQLList(type) : type - fields[comparatorFn] = { type: fieldType } - return fields - }, - {}, - ), - }) -} - -export const comparatorTypes = { - IdQueryType: createComparatorGraphQLInputType( - 'IdQueryType', - stringComparators, - GraphQLID, - ), - StringQueryType: createComparatorGraphQLInputType( - 'StringQueryType', - stringComparators, - GraphQLString, - ), - IntQueryType: createComparatorGraphQLInputType( - 'IntQueryType', - numberComparators, - GraphQLInt, - ), - BooleanQueryType: createComparatorGraphQLInputType( - 'BooleanQueryType', - booleanComparators, - GraphQLBoolean, - ), -} - -export function getQueryTypeByValueType( - valueType: GraphQLScalarType, -): GraphQLInputObjectType { - switch (valueType.name) { - case 'ID': - return comparatorTypes.IdQueryType - case 'Int': - return comparatorTypes.IntQueryType - case 'Boolean': - return comparatorTypes.BooleanQueryType - default: - return comparatorTypes.StringQueryType - } -} - -export function definitionToFields( - definition: ModelDefinition, -): GraphQLFieldsMap { - return Object.entries(definition).reduce( - (types, [key, value]) => { - const valueType = - value instanceof PrimaryKey ? GraphQLID : getGraphQLType(value) - const queryType = getQueryTypeByValueType(valueType) - - // Fields describe an entity type. - types.fields[key] = { type: valueType } - - // Input fields describe a type that can be used - // as an input when creating entities (initial values). - types.inputFields[key] = { type: valueType } - - // Query input fields describe a type that is used - // as a "where" query, with its comparator function types. - types.queryInputFields[key] = { type: queryType } - - return types - }, - { - fields: {}, - inputFields: {}, - queryInputFields: {}, - } as GraphQLFieldsMap, - ) -} - -export function generateGraphQLSchema< - Dictionary extends ModelDictionary, - ModelName extends string, ->( - modelName: ModelName, - definition: ModelDefinition, - model: ModelAPI, -): GraphQLSchema { - const pluralModelName = pluralize(modelName) - const capitalModelName = capitalize(modelName) - const { fields, inputFields, queryInputFields } = - definitionToFields(definition) - - const EntityType = new GraphQLObjectType({ - name: capitalModelName, - fields, - }) - const EntityInputType = new GraphQLInputObjectType({ - name: `${capitalModelName}Input`, - fields: inputFields, - }) - const EntityQueryInputType = new GraphQLInputObjectType({ - name: `${capitalModelName}QueryInput`, - fields: queryInputFields, - }) - - const paginationArgs: GraphQLFieldConfigArgumentMap = { - take: { type: GraphQLInt }, - skip: { type: GraphQLInt }, - cursor: { type: GraphQLID }, - } - - const objectSchema = new GraphQLSchema({ - query: new GraphQLObjectType({ - name: 'Query', - fields: { - // Get an entity by the primary key. - [modelName]: { - type: EntityType, - args: { - where: { type: EntityQueryInputType }, - }, - resolve(_, args) { - return model.findFirst({ where: args.where }) - }, - }, - // Get all entities. - [pluralModelName]: { - type: new GraphQLList(EntityType), - args: { - ...paginationArgs, - where: { type: EntityQueryInputType }, - }, - resolve(_, args) { - const shouldQuery = Object.keys(args).length > 0 - - return shouldQuery - ? model.findMany({ - where: args.where, - skip: args.skip, - take: args.take, - cursor: args.cursor, - }) - : model.getAll() - }, - }, - }, - }), - mutation: new GraphQLObjectType({ - name: 'Mutation', - fields: { - // Create a new entity. - [`create${capitalModelName}`]: { - type: EntityType, - args: { - data: { type: EntityInputType }, - }, - resolve(_, args) { - return model.create(args.data) - }, - }, - // Update an single entity. - [`update${capitalModelName}`]: { - type: EntityType, - args: { - where: { type: EntityQueryInputType }, - data: { type: EntityInputType }, - }, - resolve(_, args) { - return model.update({ - where: args.where, - data: args.data, - }) - }, - }, - // Update multiple existing entities. - [`update${capitalize(pluralModelName)}`]: { - type: new GraphQLList(EntityType), - args: { - where: { type: EntityQueryInputType }, - data: { type: EntityInputType }, - }, - resolve(_, args) { - return model.updateMany({ - where: args.where, - data: args.data, - }) - }, - }, - // Delete a single entity. - [`delete${capitalModelName}`]: { - type: EntityType, - args: { - where: { type: EntityQueryInputType }, - }, - resolve(_, args) { - return model.delete({ where: args.where }) - }, - }, - // Delete multiple entities. - [`delete${capitalize(pluralModelName)}`]: { - type: new GraphQLList(EntityType), - args: { - where: { type: EntityQueryInputType }, - }, - resolve(_, args) { - return model.deleteMany({ where: args.where }) - }, - }, - }, - }), - }) - - return objectSchema -} - -export function generateGraphQLHandlers< - Dictionary extends ModelDictionary, - ModelName extends string, ->( - modelName: ModelName, - definition: ModelDefinition, - model: ModelAPI, - baseUrl: string = '', -): GraphQLHandler[] { - const target = baseUrl ? graphql.link(baseUrl) : graphql - - const objectSchema = generateGraphQLSchema(modelName, definition, model) - - return [ - target.operation(async (req, res, ctx) => { - if (!req.body) { - return - } - - const result = await executeGraphQL({ - schema: objectSchema, - source: req.body?.query, - variableValues: req.variables, - }) - - return res(ctx.data(result.data!), ctx.errors(result.errors)) - }), - ] -} diff --git a/src/model/generateRestHandlers.ts b/src/model/generateRestHandlers.ts deleted file mode 100644 index db3043a2..00000000 --- a/src/model/generateRestHandlers.ts +++ /dev/null @@ -1,228 +0,0 @@ -import pluralize from 'pluralize' -import { RestContext, RestRequest, ResponseResolver, rest } from 'msw' -import { - Entity, - ModelDictionary, - ModelAPI, - PrimaryKeyType, - ModelDefinition, -} from '../glossary' -import { QuerySelectorWhere, WeakQuerySelectorWhere } from '../query/queryTypes' -import { OperationErrorType, OperationError } from '../errors/OperationError' -import { findPrimaryKey } from '../utils/findPrimaryKey' -import { PrimaryKey } from '../primaryKey' - -enum HTTPErrorType { - BadRequest, -} - -const ErrorType = { ...HTTPErrorType, ...OperationErrorType } - -class HTTPError extends OperationError { - constructor(type: HTTPErrorType, message?: string) { - super(type, message) - this.name = 'HTTPError' - } -} - -type RequestParams = { - [K in Key]: string -} - -export function createUrlBuilder(baseUrl?: string) { - return (path: string) => { - const url = new URL(path, baseUrl || 'http://localhost') - return baseUrl ? url.toString() : url.pathname - } -} - -export function getResponseStatusByErrorType( - error: OperationError | HTTPError, -): number { - switch (error.type) { - case ErrorType.EntityNotFound: - return 404 - case ErrorType.DuplicatePrimaryKey: - return 409 - case ErrorType.BadRequest: - return 400 - default: - return 500 - } -} - -export function withErrors( - handler: ResponseResolver< - RestRequest, - RestContext - >, -): ResponseResolver< - RestRequest, - RestContext -> { - return (req, res, ctx) => { - try { - return handler(req, res, ctx) - } catch (error) { - return res( - ctx.status(getResponseStatusByErrorType(error)), - ctx.json({ - message: error.message, - }), - ) - } - } -} - -export function parseQueryParams( - modelName: ModelName, - definition: ModelDefinition, - searchParams: URLSearchParams, -) { - const paginationKeys = ['cursor', 'skip', 'take'] - const cursor = searchParams.get('cursor') - const rawSkip = searchParams.get('skip') - const rawTake = searchParams.get('take') - - const filters: QuerySelectorWhere = {} - const skip = rawSkip == null ? rawSkip : parseInt(rawSkip, 10) - const take = rawTake == null ? rawTake : parseInt(rawTake, 10) - - searchParams.forEach((value, key) => { - if (paginationKeys.includes(key)) { - return - } - - if (definition[key]) { - filters[key] = { - equals: value, - } - } else { - throw new HTTPError( - HTTPErrorType.BadRequest, - `Failed to query the "${modelName}" model: unknown property "${key}".`, - ) - } - }) - - return { - cursor, - skip, - take, - filters, - } -} - -export function generateRestHandlers< - Dictionary extends ModelDictionary, - ModelName extends string, ->( - modelName: ModelName, - modelDefinition: ModelDefinition, - model: ModelAPI, - baseUrl: string = '', -) { - const primaryKey = findPrimaryKey(modelDefinition)! - const primaryKeyValue = ( - modelDefinition[primaryKey] as PrimaryKey - ).getPrimaryKeyValue() - const modelPath = pluralize(modelName) - const buildUrl = createUrlBuilder(baseUrl) - - function extractPrimaryKey(params: Record): PrimaryKeyType { - const parameterValue = params[primaryKey] - return typeof primaryKeyValue === 'number' - ? Number(parameterValue) - : parameterValue - } - - return [ - rest.get( - buildUrl(modelPath), - withErrors>((req, res, ctx) => { - const { skip, take, cursor, filters } = parseQueryParams( - modelName, - modelDefinition, - req.url.searchParams, - ) - - let options = { where: filters } - if (take || skip) { - options = Object.assign(options, { take, skip }) - } - if (take || cursor) { - options = Object.assign(options, { take, cursor }) - } - - const records = model.findMany(options) - - return res(ctx.json(records)) - }), - ), - rest.get( - buildUrl(`${modelPath}/:${primaryKey}`), - withErrors, RequestParams>( - (req, res, ctx) => { - const id = extractPrimaryKey(req.params) - const where: WeakQuerySelectorWhere = { - [primaryKey]: { - equals: id as string, - }, - } - const entity = model.findFirst({ - strict: true, - where: where as any, - }) - - return res(ctx.json(entity)) - }, - ), - ), - rest.post( - buildUrl(modelPath), - withErrors>((req, res, ctx) => { - const createdEntity = model.create(req.body) - return res(ctx.status(201), ctx.json(createdEntity)) - }), - ), - rest.put( - buildUrl(`${modelPath}/:${primaryKey}`), - withErrors, RequestParams>( - (req, res, ctx) => { - const id = extractPrimaryKey(req.params) - const where: WeakQuerySelectorWhere = { - [primaryKey]: { - equals: id as string, - }, - } - const updatedEntity = model.update({ - strict: true, - where: where as any, - data: req.body, - })! - - return res(ctx.json(updatedEntity)) - }, - ), - ), - rest.delete( - buildUrl(`${modelPath}/:${primaryKey}`), - withErrors, RequestParams>( - (req, res, ctx) => { - const id = extractPrimaryKey(req.params) - const where: WeakQuerySelectorWhere = { - [primaryKey]: { - equals: id as string, - }, - } - const deletedEntity = model.delete({ - strict: true, - where: where as any, - })! - - return res(ctx.json(deletedEntity)) - }, - ), - ), - ] -} diff --git a/src/model/parseModelDefinition.ts b/src/model/parseModelDefinition.ts deleted file mode 100644 index 7060a349..00000000 --- a/src/model/parseModelDefinition.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { debug } from 'debug' -import { invariant } from 'outvariant' -import { - ModelDefinition, - PrimaryKeyType, - ModelDictionary, - NestedModelDefinition, -} from '../glossary' -import { PrimaryKey } from '../primaryKey' -import { isObject } from '../utils/isObject' -import { Relation, RelationsList } from '../relations/Relation' -import { NullableProperty } from '../nullable' - -const log = debug('parseModelDefinition') - -export interface ParsedModelDefinition { - primaryKey: PrimaryKeyType - properties: Array - relations: RelationsList -} - -/** - * Recursively parses a given model definition into properties and relations. - */ -function deepParseModelDefinition( - dictionary: Dictionary, - modelName: string, - definition: ModelDefinition, - parentPath?: string[], - result: ParsedModelDefinition = { - primaryKey: undefined!, - properties: [], - relations: [], - }, -) { - if (parentPath) { - log( - 'parsing a nested model definition for "%s" property at "%s"', - parentPath, - modelName, - definition, - ) - } - - for (const [propertyName, value] of Object.entries(definition)) { - const propertyPath = parentPath - ? [...parentPath, propertyName] - : [propertyName] - - // Primary key. - if (value instanceof PrimaryKey) { - invariant( - !result.primaryKey, - 'Failed to parse a model definition for "%s": cannot have both properties "%s" and "%s" as a primary key.', - modelName, - result.primaryKey, - propertyName, - ) - - invariant( - !parentPath, - 'Failed to parse a model definition for "%s" property of "%s": cannot have a primary key in a nested object.', - parentPath?.join('.'), - modelName, - ) - - result.primaryKey = propertyName - result.properties.push([propertyName]) - - continue - } - - if (value instanceof NullableProperty) { - // Add nullable properties to the same list as regular properties - result.properties.push(propertyPath) - continue - } - - // Relations. - if (value instanceof Relation) { - // Store the relations in a separate object. - result.relations.push({ propertyPath, relation: value }) - continue - } - - // Nested objects. - if (isObject(value)) { - deepParseModelDefinition( - dictionary, - modelName, - value, - propertyPath, - result, - ) - - continue - } - - // Regular properties. - result.properties.push(propertyPath) - } - - return result -} - -export function parseModelDefinition( - dictionary: Dictionary, - modelName: string, - definition: ModelDefinition, -): ParsedModelDefinition { - log('parsing model definition for "%s" entity', modelName, definition) - const result = deepParseModelDefinition(dictionary, modelName, definition) - - invariant( - result.primaryKey, - 'Failed to parse a model definition for "%s": model is missing a primary key. Did you forget to mark one of its properties using the "primaryKey" function?', - modelName, - ) - - return result -} diff --git a/src/model/updateEntity.ts b/src/model/updateEntity.ts deleted file mode 100644 index 7b282479..00000000 --- a/src/model/updateEntity.ts +++ /dev/null @@ -1,202 +0,0 @@ -import { debug } from 'debug' -import get from 'lodash/get' -import set from 'lodash/set' -import { invariant } from 'outvariant' -import { Relation, RelationKind } from '../relations/Relation' -import { ENTITY_TYPE, PRIMARY_KEY, Entity, ModelDefinition } from '../glossary' -import { isObject } from '../utils/isObject' -import { inheritInternalProperties } from '../utils/inheritInternalProperties' -import { NullableProperty } from '../nullable' -import { spread } from '../utils/spread' - -const log = debug('updateEntity') - -/** - * Update an entity with the given next data. - */ -export function updateEntity( - entity: Entity, - data: Record, - definition: ModelDefinition, -): Entity { - log('updating entity:\n%j\nwith data:\n%j', entity, data) - log('model definition:', definition) - - const nextEntity = spread(entity) - inheritInternalProperties(nextEntity, entity) - - const updateRecursively = (data: any, parentPath: string[] = []): void => { - log('updating path "%s" to:', parentPath, data) - - for (const [propertyName, value] of Object.entries(data)) { - const propertyPath = parentPath.concat(propertyName) - - const prevValue = get(nextEntity, propertyPath) - log('previous value for "%s":', propertyPath, prevValue) - - const nextValue = - typeof value === 'function' ? value(prevValue, entity) : value - log('next value for "%s":', propertyPath, nextValue) - - const propertyDefinition = get(definition, propertyPath) - log('property definition for "%s":', propertyPath, propertyDefinition) - - if (propertyDefinition == null) { - log( - 'skipping an unknown property "%s" on "%s"...', - propertyName, - entity[ENTITY_TYPE], - ) - continue - } - - if (propertyDefinition instanceof Relation) { - log( - 'property "%s" is a "%s" relationship to "%s"', - propertyPath, - propertyDefinition.kind, - propertyDefinition.target.modelName, - ) - - const location = `${nextEntity[ENTITY_TYPE]}.${propertyPath.join('.')}` - - if (nextValue == null) { - // Forbid updating a non-nullable relationship to null. - invariant( - propertyDefinition.attributes.nullable, - 'Failed to update a "%s" relationship to "%s" at "%s" (%s: "%s"): cannot update a non-nullable relationship to null.', - propertyDefinition.kind, - propertyDefinition.target.modelName, - location, - nextEntity[PRIMARY_KEY], - nextEntity[nextEntity[PRIMARY_KEY]], - ) - - log( - 're-defining the "%s" relationship on "%s" to: null', - propertyName, - nextEntity[ENTITY_TYPE], - ) - propertyDefinition.resolveWith(nextEntity, null) - continue - } - - if (propertyDefinition.kind === RelationKind.ManyOf) { - // Forbid updating a "MANY_OF" relation to a non-array value. - invariant( - Array.isArray(nextValue), - 'Failed to update a "%s" relationship to "%s" at "%s" (%s: "%s"): expected the next value to be an array of entities but got %j.', - propertyDefinition.kind, - propertyDefinition.target.modelName, - location, - nextEntity[PRIMARY_KEY], - nextEntity[nextEntity[PRIMARY_KEY]], - nextValue, - ) - - nextValue.forEach((ref, index) => { - // Forbid providing a compatible plain object in any array members. - invariant( - ref[ENTITY_TYPE], - 'Failed to update a "%s" relationship to "%s" at "%s" (%s: "%s"): expected the next value at index %d to be an entity but got %j.', - propertyDefinition.kind, - propertyDefinition.target.modelName, - location, - nextEntity[PRIMARY_KEY], - nextEntity[nextEntity[PRIMARY_KEY]], - index, - ref, - ) - - // Forbid referencing a different model in any array members. - invariant( - ref[ENTITY_TYPE] === propertyDefinition.target.modelName, - 'Failed to update a "%s" relationship to "%s" at "%s" (%s: "%s"): expected the next value at index %d to reference a "%s" but got "%s".', - propertyDefinition.kind, - propertyDefinition.target.modelName, - location, - nextEntity[PRIMARY_KEY], - nextEntity[nextEntity[PRIMARY_KEY]], - index, - propertyDefinition.target.modelName, - ref[ENTITY_TYPE], - ) - }) - - propertyDefinition.resolveWith(nextEntity, nextValue) - continue - } - - // Forbid updating a relationship with a compatible plain object. - invariant( - nextValue[ENTITY_TYPE], - 'Failed to update a "%s" relationship to "%s" at "%s" (%s: "%s"): expected the next value to be an entity but got %j.', - propertyDefinition.kind, - propertyDefinition.target.modelName, - location, - nextEntity[PRIMARY_KEY], - nextEntity[nextEntity[PRIMARY_KEY]], - nextValue, - ) - - // Forbid updating a relationship to an entity of a different model. - invariant( - nextValue[ENTITY_TYPE] == propertyDefinition.target.modelName, - 'Failed to update a "%s" relationship to "%s" at "%s" (%s: "%s"): expected the next value to reference a "%s" but got "%s" (%s: "%s").', - propertyDefinition.kind, - propertyDefinition.target.modelName, - location, - nextEntity[PRIMARY_KEY], - nextEntity[nextEntity[PRIMARY_KEY]], - propertyDefinition.target.modelName, - nextValue[ENTITY_TYPE], - nextValue[PRIMARY_KEY], - nextValue[nextValue[PRIMARY_KEY]], - ) - - // Re-define the relationship only if its next value references a different entity - // than before. That means a new compatible entity was created as the next value. - if ( - prevValue?.[prevValue?.[PRIMARY_KEY]] !== - nextValue[nextValue[PRIMARY_KEY]] - ) { - log( - 'next referenced "%s" (%s: "%s") differs from the previous (%s: "%s"), re-defining the relationship...', - propertyDefinition.target.modelName, - nextValue[PRIMARY_KEY], - ) - propertyDefinition.resolveWith(nextEntity, nextValue) - } - - continue - } - - // Support updating nested objects. - if (isObject(nextValue)) { - log( - 'next value at "%s" is an object: %j, recursively updating...', - propertyPath, - nextValue, - ) - updateRecursively(nextValue, propertyPath) - continue - } - - invariant( - nextValue !== null || propertyDefinition instanceof NullableProperty, - 'Failed to update "%s" on "%s": cannot set a non-nullable property to null.', - propertyName, - entity[ENTITY_TYPE], - ) - - log('updating a plain property "%s" to:', propertyPath, nextValue) - set(nextEntity, propertyPath, nextValue) - } - } - - updateRecursively(data) - - log('successfully updated to:', nextEntity) - - return nextEntity -} diff --git a/src/nullable.ts b/src/nullable.ts deleted file mode 100644 index 585b0d5e..00000000 --- a/src/nullable.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { ModelValueType } from './glossary' -import { ManyOf, OneOf, Relation, RelationKind } from './relations/Relation' - -export type NullableGetter = - () => ValueType | null - -export class NullableProperty { - public getValue: NullableGetter - - constructor(getter: NullableGetter) { - this.getValue = getter - } -} - -export function nullable( - value: NullableGetter, -): NullableProperty - -export function nullable< - ValueType extends Relation, ->( - value: ValueType, -): ValueType extends Relation - ? Kind extends RelationKind.ManyOf - ? ManyOf - : OneOf - : never - -export function nullable( - value: - | NullableGetter - | Relation, -) { - if (typeof value === 'function') { - return new NullableProperty(value) - } - - return new Relation({ - kind: value.kind, - to: value.target.modelName, - attributes: { - ...value.attributes, - nullable: true, - }, - }) -} diff --git a/src/primaryKey.ts b/src/primaryKey.ts deleted file mode 100644 index 6d58c994..00000000 --- a/src/primaryKey.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { PrimaryKeyType } from './glossary' - -export type PrimaryKeyGetter = () => ValueType - -export class PrimaryKey { - public getPrimaryKeyValue: PrimaryKeyGetter - - constructor(getter: PrimaryKeyGetter) { - this.getPrimaryKeyValue = getter - } -} - -export function primaryKey( - getter: PrimaryKeyGetter, -): PrimaryKey { - return new PrimaryKey(getter) -} diff --git a/src/query.ts b/src/query.ts new file mode 100644 index 00000000..cd8c1f0a --- /dev/null +++ b/src/query.ts @@ -0,0 +1,129 @@ +import { isObject } from '#/src/utils.js' + +export type Condition = + NonNullable extends Array + ? Condition | PredicateFunction | Extract + : T extends Record + ? { + [K in keyof T]?: NonNullable extends Array + ? + | Condition + | PredicateFunction + | Extract + : /** + * @note Cast T[K] to NonNullable to match + * Record | undefined, too. + */ + NonNullable extends Record + ? Condition | Extract + : T[K] | PredicateFunction + } + : never + +export type PredicateFunction = (value: T) => unknown + +export class Query { + #predicate?: PredicateFunction + + constructor(predicate?: PredicateFunction>) { + this.#predicate = predicate + } + + public test(value: T): boolean { + return !!this.#predicate?.(value) + } + + public where(condition: Condition | PredicateFunction) { + return new Query( + Query.#and(this.#predicate, Query.#normalize(condition)), + ) + } + + public and( + ...conditions: Array | Condition | PredicateFunction> + ) { + return new Query( + Query.#and(this.#predicate, ...conditions.map(Query.#normalize)), + ) + } + + public or( + ...conditions: Array | Condition | PredicateFunction> + ) { + return new Query( + Query.#or(this.#predicate, ...conditions.map(Query.#normalize)), + ) + } + + static #normalize( + condition: Query | Condition | PredicateFunction, + ): PredicateFunction | undefined { + if (condition instanceof Query) { + return condition.#predicate + } + + if (typeof condition === 'function') { + return condition as PredicateFunction + } + + if (isObject(condition)) { + function compileCondition( + condition: Condition, + ): PredicateFunction { + return (record) => { + if (Array.isArray(record)) { + return record.every((item) => compileCondition(condition)(item)) + } + + return Object.entries(condition).every(([key, selector]) => { + const actualValue = record[key] + + if (actualValue === undefined) { + return false + } + + if (Array.isArray(actualValue)) { + return actualValue.every((value) => { + return compileCondition(selector)(value) + }) + } + + if (isObject(actualValue)) { + return compileCondition(selector)(actualValue) + } + + if (typeof selector === 'function') { + return selector(actualValue) + } + + return actualValue === selector + }) + } + } + + return compileCondition(condition) + } + + throw new TypeError('Invalid condition type') + } + + static #and( + ...predicates: Array | undefined> + ): PredicateFunction | undefined { + return (value) => { + return predicates.filter(Boolean).every((predicate) => { + return predicate?.(value) + }) + } + } + + static #or( + ...predicates: Array | undefined> + ): PredicateFunction | undefined { + return (value) => { + return predicates.filter(Boolean).some((predicate) => { + return predicate?.(value) + }) + } + } +} diff --git a/src/query/compileQuery.ts b/src/query/compileQuery.ts deleted file mode 100644 index 5f6d3acd..00000000 --- a/src/query/compileQuery.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { debug } from 'debug' -import { invariant } from 'outvariant' -import { ComparatorFn, QuerySelector } from './queryTypes' -import { getComparatorsForValue } from './getComparatorsForValue' -import { isObject } from '../utils/isObject' - -const log = debug('compileQuery') - -/** - * Compile a query expression into a function that accepts an actual entity - * and returns a query execution result (whether the entity satisfies the query). - */ -export function compileQuery>( - query: QuerySelector, -) { - log('%j', query) - - return (data: Data): boolean => { - return Object.entries(query.where) - .map(([property, queryChunk]) => { - const actualValue = data[property] - - log( - 'executing query chunk on "%s":\n\n%j\n\non data:\n\n%j\n', - property, - queryChunk, - data, - ) - log('actual value for "%s":', property, actualValue) - - if (!queryChunk) { - return true - } - - // If an entity doesn't have any value for the property - // is being queried for, treat it as non-matching. - if (actualValue == null) { - return false - } - - return Object.entries(queryChunk).reduce( - (acc, [comparatorName, expectedValue]) => { - if (!acc) { - return acc - } - - if (Array.isArray(actualValue)) { - log( - 'actual value is array, checking if at least one item matches...', - { - comparatorName, - expectedValue, - }, - ) - - /** - * @fixme Can assume `some`? Why not `every`? - */ - return actualValue.some((value) => { - return compileQuery({ where: queryChunk })(value) - }) - } - - // When the actual value is a resolved relational property reference, - // execute the current query chunk on the referenced entity. - if (actualValue.__type || isObject(actualValue)) { - return compileQuery({ where: queryChunk })(actualValue) - } - - const comparatorSet = getComparatorsForValue(actualValue) - log('comparators', comparatorSet) - - const comparatorFn = (comparatorSet as any)[ - comparatorName - ] as ComparatorFn - - log( - 'using comparator function for "%s":', - comparatorName, - comparatorFn, - ) - - invariant( - comparatorFn, - 'Failed to compile the query "%j": no comparator found for the chunk "%s". Please check the validity of the query.', - query, - comparatorName, - ) - - return comparatorFn(expectedValue, actualValue) - }, - true, - ) - }) - .every(Boolean) - } -} diff --git a/src/query/executeQuery.ts b/src/query/executeQuery.ts deleted file mode 100644 index 7933d322..00000000 --- a/src/query/executeQuery.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { debug } from 'debug' -import { Entity, PrimaryKeyType, PRIMARY_KEY } from '../glossary' -import { compileQuery } from './compileQuery' -import { - BulkQueryOptions, - QuerySelector, - WeakQuerySelector, -} from './queryTypes' -import * as iteratorUtils from '../utils/iteratorUtils' -import { paginateResults } from './paginateResults' -import { Database } from '../db/Database' -import { sortResults } from './sortResults' -import { invariant } from 'outvariant' -import { safeStringify } from '../utils/safeStringify' - -const log = debug('executeQuery') - -function queryByPrimaryKey( - records: Map>, - query: QuerySelector, -) { - log('querying by primary key') - log('query by primary key', { query, records }) - - const matchPrimaryKey = compileQuery(query) - - const result = iteratorUtils.filter((id, value) => { - const primaryKey = value[PRIMARY_KEY] - - invariant( - primaryKey, - 'Failed to query by primary key using "%j": record (%j) has no primary key set.', - query, - value, - ) - - return matchPrimaryKey({ [primaryKey]: id }) - }, records) - - log('result of querying by primary key:', result) - return result -} - -/** - * Execute a given query against a model in the database. - * Returns the list of records that satisfy the query. - */ -export function executeQuery( - modelName: string, - primaryKey: PrimaryKeyType, - query: WeakQuerySelector & BulkQueryOptions, - db: Database, -): Entity[] { - log(`${safeStringify(query)} on "${modelName}"`) - log('using primary key "%s"', primaryKey) - - const records = db.getModel(modelName) - - // Reduce the query scope if there's a query by primary key of the model. - const { [primaryKey]: primaryKeyComparator, ...restQueries } = - query.where || {} - log('primary key query', primaryKeyComparator) - - const scopedRecords = primaryKeyComparator - ? queryByPrimaryKey(records, { - where: { [primaryKey]: primaryKeyComparator }, - }) - : records - - const result = iteratorUtils.filter((_, record) => { - const executeQuery = compileQuery({ where: restQueries }) - return executeQuery(record) - }, scopedRecords) - - const resultJson = Array.from(result.values()) - - log( - `resolved query "${safeStringify(query)}" on "${modelName}" to`, - resultJson, - ) - - if (query.orderBy) { - sortResults(query.orderBy, resultJson) - } - - const paginatedResults = paginateResults(query, resultJson) - log('paginated query results', paginatedResults) - - return paginatedResults -} diff --git a/src/query/getComparatorsForValue.ts b/src/query/getComparatorsForValue.ts deleted file mode 100644 index 43fbc7ac..00000000 --- a/src/query/getComparatorsForValue.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { booleanComparators } from '../comparators/boolean' -import { dateComparators } from '../comparators/date' -import { numberComparators } from '../comparators/number' -import { stringComparators } from '../comparators/string' -import { - DateQuery, - NumberQuery, - StringQuery, - BooleanQuery, - QueryToComparator, -} from './queryTypes' - -export function getComparatorsForValue( - value: string | number, -): QueryToComparator { - switch (value.constructor.name) { - case 'String': - return stringComparators - - case 'Number': - return numberComparators - - case 'Boolean': - return booleanComparators - - case 'Date': - return dateComparators - - default: - throw new Error( - `Failed to find a comparator for the value "${JSON.stringify( - value, - )}" of type "${value.constructor.name}".`, - ) - } -} diff --git a/src/query/paginateResults.ts b/src/query/paginateResults.ts deleted file mode 100644 index e8b27385..00000000 --- a/src/query/paginateResults.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Entity, PRIMARY_KEY } from '../glossary' -import { BulkQueryOptions, WeakQuerySelector } from './queryTypes' - -function getEndIndex(start: number, end?: number) { - return end ? start + end : undefined -} - -export function paginateResults( - query: WeakQuerySelector & BulkQueryOptions, - data: Entity[], -): Entity[] { - if (query.cursor) { - const cursorIndex = data.findIndex((entity) => { - return entity[entity[PRIMARY_KEY]] === query.cursor - }) - - if (cursorIndex === -1) { - return [] - } - - return data.slice(cursorIndex + 1, getEndIndex(cursorIndex + 1, query.take)) - } - - const start = query.skip || 0 - return data.slice(start, getEndIndex(start, query.take)) -} diff --git a/src/query/queryTypes.ts b/src/query/queryTypes.ts deleted file mode 100644 index 86de35b9..00000000 --- a/src/query/queryTypes.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { - AnyObject, - DeepRequiredExactlyOne, - PrimaryKeyType, - Value, - ModelValueType, - ModelDefinitionValue, -} from '../glossary' - -export interface QueryOptions { - strict?: boolean -} -export interface QuerySelector { - where: QuerySelectorWhere -} - -export type WeakQuerySelector = Partial< - QuerySelector -> - -export type RecursiveQuerySelectorWhere = - Value extends Array - ? Partial> - : Value extends ModelValueType - ? Partial> - : Value extends AnyObject - ? { - [K in keyof Value]?: RecursiveQuerySelectorWhere - } - : never - -export type QuerySelectorWhere = { - [Key in keyof EntityType]?: RecursiveQuerySelectorWhere -} - -export interface WeakQuerySelectorWhere { - [key: string]: Partial> -} - -export type SortDirection = 'asc' | 'desc' - -export type RecursiveOrderBy = - Value extends ModelValueType - ? SortDirection - : Value extends AnyObject - ? DeepRequiredExactlyOne<{ - [K in keyof Value]?: RecursiveOrderBy - }> - : never - -export type OrderBy = DeepRequiredExactlyOne<{ - [Key in keyof EntityType]?: RecursiveOrderBy -}> - -export interface BulkQueryBaseOptions { - take?: number - orderBy?: OrderBy | OrderBy[] -} - -interface BulkQueryOffsetOptions - extends BulkQueryBaseOptions { - skip?: number - cursor?: never -} - -interface BulkQueryCursorOptions - extends BulkQueryBaseOptions { - skip?: never - cursor: PrimaryKeyType | null -} - -export type BulkQueryOptions = - | BulkQueryOffsetOptions - | BulkQueryCursorOptions - -export type ComparatorFn = ( - expected: ExpectedType, - actual: ActualType, -) => boolean - -export type QueryToComparator< - QueryType extends StringQuery | NumberQuery | BooleanQuery | DateQuery, -> = { - [Key in keyof QueryType]: ComparatorFn< - QueryType[Key], - QueryType[Key] extends Array ? ValueType : QueryType[Key] - > -} - -export type GetQueryFor = ValueType extends string - ? StringQuery - : ValueType extends number - ? NumberQuery - : ValueType extends Boolean - ? BooleanQuery - : ValueType extends Date - ? DateQuery - : ValueType extends Array - ? QuerySelector['where'] - : /** - * Relational `oneOf`/`manyOf` invocation - * resolves to the `Value` type. - */ - ValueType extends Value - ? QuerySelector['where'] - : never - -export interface StringQuery { - equals: string - notEquals: string - contains: string - notContains: string - in: string[] - notIn: string[] -} - -export interface NumberQuery { - equals: number - notEquals: number - between: [number, number] - notBetween: [number, number] - gt: number - gte: number - lt: number - lte: number - in: number[] - notIn: number[] -} - -export interface BooleanQuery { - equals: boolean - notEquals: boolean -} - -export interface DateQuery { - equals: Date - notEquals: Date - gt: Date - gte: Date - lt: Date - lte: Date -} diff --git a/src/query/sortResults.ts b/src/query/sortResults.ts deleted file mode 100644 index 5c356ca3..00000000 --- a/src/query/sortResults.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { debug } from 'debug' -import get from 'lodash/get' -import { Entity } from 'src/glossary' -import { OrderBy, SortDirection } from './queryTypes' - -const log = debug('sortResults') - -type FlatSortCriteria = [string[], SortDirection] - -function warnOnIneffectiveSortingKeys(sortCriteria: Record): void { - const [mainCriteria, ...siblings] = Object.keys(sortCriteria) - - if (siblings.length > 0) { - console.warn( - 'Sorting by "%s" has no effect: already sorted by "%s".', - siblings.join(','), - mainCriteria, - ) - } -} - -function flattenSortCriteria>( - orderBy: OrderBy[], - propertyPath: string[] = [], -): FlatSortCriteria[] { - log('flattenSortCriteria:', orderBy, propertyPath) - - return orderBy.reduce((criteria, properties) => { - warnOnIneffectiveSortingKeys(properties) - - // Multiple properties in a single criteria object are forbidden. - // Use the list of criteria objects for multi-criteria sort. - const property = Object.keys(properties)[0] as keyof OrderBy - const sortDirection = properties[property]! - const path = propertyPath.concat(property.toString()) - log({ property, sortDirection, path }) - - // Recursively flatten order criteria when referencing - // relational properties. - const newCriteria = - typeof sortDirection === 'object' - ? flattenSortCriteria([sortDirection], path) - : ([[path, sortDirection]] as FlatSortCriteria[]) - - log('pushing new criteria:', newCriteria) - return criteria.concat(newCriteria) - }, []) -} - -/** - * Sorts the given list of entities by a certain criteria. - */ -export function sortResults>( - orderBy: OrderBy | OrderBy[], - data: Entity[], -): void { - log('sorting data:', data) - log('order by:', orderBy) - - const criteriaList = ([] as OrderBy[]).concat(orderBy) - log('criteria list:', criteriaList) - - const criteria = flattenSortCriteria(criteriaList) - log('flattened criteria:', JSON.stringify(criteria)) - - data.sort((left, right) => { - for (const [path, sortDirection] of criteria) { - const leftValue = get(left, path) - const rightValue = get(right, path) - - log( - 'comparing value at "%s" (%s): "%s" / "%s"', - path, - sortDirection, - leftValue, - rightValue, - ) - - if (leftValue > rightValue) { - return sortDirection === 'asc' ? 1 : -1 - } - - if (leftValue < rightValue) { - return sortDirection === 'asc' ? -1 : 1 - } - } - - return 0 - }) - - log('sorted results:\n', data) -} diff --git a/src/relation.ts b/src/relation.ts new file mode 100644 index 00000000..e800ce04 --- /dev/null +++ b/src/relation.ts @@ -0,0 +1,731 @@ +import type { StandardSchemaV1 } from '@standard-schema/spec' +import { invariant } from 'outvariant' +import { isEqual } from 'es-toolkit' +import { get, set, unset } from 'es-toolkit/compat' +import { + kPrimaryKey, + kRelationMap, + kCollectionId, + type Collection, + type RecordType, +} from '#/src/collection.js' +import { Logger } from '#/src/logger.js' +import { + definePropertyAtPath, + isRecord, + type PropertyPath, +} from '#/src/utils.js' +import { + RelationError, + RelationErrorCodes, + type RelationErrorDetails, +} from '#/src/errors.js' + +export interface RelationDeclarationOptions { + /** + * Unique relation role to disambiguate between multiple relations + * to the same target collection. + */ + role?: string + + /** + * Marks this relation as unique. + * Unique relations cannot reference foreign records that is already + * associated with other owner records by the same relation. + */ + unique?: boolean + + /** + * Decides how to handle the deletion of the referenced record. + * - `cascade`: Delete all owner records referencing the deleted record. + * + * By default, removes the reference to the deleted record from the owner record. + */ + onDelete?: 'cascade' +} + +export type RelationsFunction> = ( + builder: RelationBuilder, +) => RelationMapDefinition + +type RelationMapDefinition> = + T extends Array + ? RelationMapDefinition + : T extends Record + ? { + [K in keyof T]?: RelationMapDefinition | (() => Relation) + } + : () => Relation + +export interface RelationBuilder { + one: ( + foreignCollection: Collection | Array>, + options?: RelationDeclarationOptions, + ) => () => Relation + many: ( + foreignCollection: Collection | Array>, + options?: RelationDeclarationOptions, + ) => () => Relation +} + +export const createRelationBuilder = >( + ownerCollection: Owner, +): RelationBuilder => { + return { + one(foreignCollection, options) { + return () => + new One( + ownerCollection, + Array.isArray(foreignCollection) + ? foreignCollection + : [foreignCollection], + options, + ) + }, + many(foreignCollection, options) { + return () => + new Many( + ownerCollection, + Array.isArray(foreignCollection) + ? foreignCollection + : [foreignCollection], + options, + ) + }, + } +} + +export abstract class Relation { + #logger: Logger + #path?: PropertyPath + + public foreignKeys: Set + + constructor( + readonly ownerCollection: Collection, + /** The list of collecftions referenced by this relation. */ + readonly foreignCollections: Array>, + readonly options: RelationDeclarationOptions = {}, + ) { + this.#logger = new Logger('Relation') + this.#logger.log('creating relation:', { + ownerCollection, + foreignCollections, + options, + }) + + this.foreignKeys = new Set() + } + + get path(): PropertyPath { + invariant( + this.#path != null, + 'Failed to retrieve path for relation: relation is not initialized', + ) + + return this.#path + } + + /** + * Initializes the relation on the owner record. + */ + public initialize( + record: RecordType, + path: Array, + initialValues: unknown, + ) { + this.#path = path + const serializedPath = path.join('.') + + // Whenever a new record is created, initialize its relations. + // This way, each record has its own instance of a stateful relation. + this.#initializeRelation(path, record, initialValues) + + /** + * @note Tear down all hook listeners registered below once the owner + * record is deleted. Aborting the signal removes the listeners from + * the underlying emitter — including the `delete` listener itself. + */ + const abortController = new AbortController() + + this.ownerCollection.hooks.on( + 'delete', + (event) => { + if ( + event.data.deletedRecord[kRelationMap].get(serializedPath) === this + ) { + abortController.abort() + } + }, + { signal: abortController.signal }, + ) + + for (const foreignCollection of this.foreignCollections) { + // Update the owner relations when a foreign record is created + // referencing the owner record. + foreignCollection.hooks.on( + 'create', + (event) => { + const { record: foreignRecord } = event.data + const foreignRelations = this.getRelationsToOwner(foreignRecord) + + for (const foreignRelation of foreignRelations) { + const ownerRecords = this.ownerCollection.findMany((q) => + q.where((record) => { + return foreignRelation.foreignKeys.has(record[kPrimaryKey]) + }), + ) + + for (const ownerRecord of ownerRecords) { + const ownerRelation = + ownerRecord[kRelationMap].get(serializedPath) + ownerRelation.foreignKeys.add(foreignRecord[kPrimaryKey]) + } + } + }, + { signal: abortController.signal }, + ) + + // Clear the references to deleted foreign records. + foreignCollection.hooks.on( + 'delete', + (event) => { + const { deletedRecord: deletedForeignRecord } = event.data + this.foreignKeys.delete(deletedForeignRecord[kPrimaryKey]) + + // Delete all the owners referencing the deleted foreign record + // if the relation is set to cascade on delete. + if (this.options.onDelete === 'cascade') { + const foreignRelations = + this.getRelationsToOwner(deletedForeignRecord) + + this.ownerCollection.deleteMany((q) => { + return q.where((record) => { + return foreignRelations.some((foreignRelation) => { + return foreignRelation.foreignKeys.has(record[kPrimaryKey]) + }) + }) + }) + } + }, + { signal: abortController.signal }, + ) + } + + /** + * @note Handle foreign record updates through the owner record in an early hook + * because if such an update occurs, it must NOT translate to the owner "update" event + * (the owner had no updates, it just updated a foreign record through itself). + * + * @example + * await users.update(q, { data: { country: { code: 'uk' } } }) + */ + this.ownerCollection.hooks.earlyOn( + 'update', + (event) => { + const update = event.data + + if ( + update.path.length > path.length && + path.every((key, index) => key === update.path[index]) && + !isRecord(update.nextValue) + ) { + /** + * @note Listeners are attached per-record but fire for every owner update. + * Skip events whose target record's relation isn't this instance. + */ + if (update.prevRecord[kRelationMap].get(serializedPath) !== this) { + return + } + + /** + * @note For `Many` relations the first trailing segment is the array index + * selecting a specific foreign record. Identify that record and apply the + * remaining path to it. For `One` relations the whole trailing path applies + * to the single associated foreign record. + */ + let targetForeignKey: string | undefined + let foreignUpdatePath: Array + + if (this instanceof Many) { + const indexSegment = update.path[path.length] + if (typeof indexSegment !== 'number') { + return + } + const resolved = this.resolve(this.foreignKeys) + if (!Array.isArray(resolved)) { + return + } + const targetRecord = resolved[indexSegment] + if (!isRecord(targetRecord)) { + return + } + targetForeignKey = targetRecord[kPrimaryKey] + foreignUpdatePath = update.path.slice(path.length + 1) + } else { + foreignUpdatePath = update.path.slice(path.length) + } + + if (foreignUpdatePath.length === 0) { + return + } + + event.preventDefault() + event.stopImmediatePropagation() + + for (const foreignCollection of this.foreignCollections) { + foreignCollection.updateMany( + (q) => { + return q.where((record) => { + if (targetForeignKey != null) { + return record[kPrimaryKey] === targetForeignKey + } + return this.foreignKeys.has(record[kPrimaryKey]) + }) + }, + { + data(foreignRecord) { + set(foreignRecord, foreignUpdatePath, update.nextValue) + }, + }, + ) + } + } + }, + { signal: abortController.signal }, + ) + + /** + * Handle owner updates where the relational property changes to another foreign record. + * + * @example + * await users.update(q, { data: { country: await countries.create({}) } }) + */ + this.ownerCollection.hooks.on( + 'update', + (event) => { + const update = event.data + + if (!isEqual(update.path, path)) { + return + } + + /** + * @note Listeners are attached per-record but fire for every owner update. + * Skip events whose target record's relation isn't this instance. + */ + if (update.prevRecord[kRelationMap].get(serializedPath) !== this) { + return + } + + if (this instanceof One && isRecord(update.nextValue)) { + event.preventDefault() + + // If the owner relation is "one-of", multiple foreign records cannot own this record. + // Disassociate the old foreign records from pointing to the owner record. + const oldForeignRecords = + this.foreignCollections.flatMap( + (foreignCollection) => { + return foreignCollection.findMany((q) => { + return q.where((record) => { + return this.foreignKeys.has(record[kPrimaryKey]) + }) + }) + }, + ) + + const foreignRelationsToDisassociate = oldForeignRecords.flatMap( + (record) => this.getRelationsToOwner(record), + ) + + // Throw if attempting to disassociate unique relations. + if (this.options.unique) { + invariant.as( + RelationError.for( + RelationErrorCodes.FORBIDDEN_UNIQUE_UPDATE, + this.#createErrorDetails(), + ), + foreignRelationsToDisassociate.length === 0, + 'Failed to update a unique relation at "%s": the foreign record is already associated with another owner', + update.path.join('.'), + ) + } + + for (const foreignRelation of foreignRelationsToDisassociate) { + foreignRelation.foreignKeys.delete(update.prevRecord[kPrimaryKey]) + } + + // Check any other owners associated with the same foreign record. + // This is important since unique relations are not always two-way. + if (this.options.unique) { + const otherOwnersAssociatedWithForeignRecord = + this.#getOtherOwnerForRecords([update.nextValue]) + + invariant.as( + RelationError.for( + RelationErrorCodes.FORBIDDEN_UNIQUE_UPDATE, + this.#createErrorDetails(), + ), + otherOwnersAssociatedWithForeignRecord == null, + 'Failed to update a unique relation at "%s": the foreign record is already associated with another owner', + update.path.join('.'), + ) + } + + this.foreignKeys.clear() + + // Associate the owner with a foreign record from the update data. + const foreignRecord = update.nextValue + this.foreignKeys.add(foreignRecord[kPrimaryKey]) + + for (const foreignRelation of this.getRelationsToOwner( + foreignRecord, + )) { + foreignRelation.foreignKeys.add(update.prevRecord[kPrimaryKey]) + } + } else if (this instanceof Many && Array.isArray(update.nextValue)) { + event.preventDefault() + + const nextForeignRecords: Array = [] + const nextForeignKeys = new Set() + + for (const foreignRecord of update.nextValue) { + invariant.as( + RelationError.for( + RelationErrorCodes.INVALID_FOREIGN_RECORD, + this.#createErrorDetails(), + ), + isRecord(foreignRecord) && foreignRecord[kPrimaryKey] != null, + 'Failed to update a relation at "%s": expected relational value to be a record with a primary key, got "%j"', + update.path.join('.'), + foreignRecord, + ) + + nextForeignRecords.push(foreignRecord) + nextForeignKeys.add(foreignRecord[kPrimaryKey]) + } + + // Unique check for newly-added foreign records. + if (this.options.unique) { + const addedForeignRecords = nextForeignRecords.filter( + (record) => !this.foreignKeys.has(record[kPrimaryKey]), + ) + + if (addedForeignRecords.length > 0) { + const otherOwnersAssociatedWithForeignRecord = + this.#getOtherOwnerForRecords(addedForeignRecords) + + invariant.as( + RelationError.for( + RelationErrorCodes.FORBIDDEN_UNIQUE_UPDATE, + this.#createErrorDetails(), + ), + otherOwnersAssociatedWithForeignRecord == null, + 'Failed to update a unique relation at "%s": the foreign record is already associated with another owner', + update.path.join('.'), + ) + } + } + + // Disassociate inverse links for foreign records no longer referenced. + const removedForeignRecords = this.foreignCollections + .flatMap((foreignCollection) => { + return foreignCollection.findMany((q) => { + return q.where((record) => { + return this.foreignKeys.has(record[kPrimaryKey]) + }) + }) + }) + .filter((record) => !nextForeignKeys.has(record[kPrimaryKey])) + + for (const removedForeignRecord of removedForeignRecords) { + for (const foreignRelation of this.getRelationsToOwner( + removedForeignRecord, + )) { + foreignRelation.foreignKeys.delete(update.prevRecord[kPrimaryKey]) + } + } + + this.foreignKeys.clear() + for (const foreignKey of nextForeignKeys) { + this.foreignKeys.add(foreignKey) + } + + for (const foreignRecord of nextForeignRecords) { + for (const foreignRelation of this.getRelationsToOwner( + foreignRecord, + )) { + foreignRelation.foreignKeys.add(update.prevRecord[kPrimaryKey]) + } + } + } + }, + { signal: abortController.signal }, + ) + } + + public abstract resolve(foreignKeys: Set): unknown + + public abstract getDefaultValue(): unknown + + #initializeRelation( + path: Array, + record: RecordType, + initialValues: any, + ): void { + const logger = this.#logger + const serializedPath = path.join('.') + + logger.log('owner record is being created:', { + record, + path, + initialValues, + ownerCollectionId: this.ownerCollection[kCollectionId], + foreignCollectionIds: Array.from(this.foreignCollections), + }) + + const relationMap = record[kRelationMap] + logger.log('owner relation map (before update):', relationMap) + + relationMap.set(serializedPath, this) + logger.log('owner relation map (after update):', relationMap) + + // Replace literal record references in initial values + // with pointers to the foreign keys. + const initialValue = get(record, path) + + if (initialValue != null) { + logger.log( + `found initial value for "${serializedPath}" relation:`, + initialValue, + ) + + const initialForeignRecords: Array = Array.prototype + .concat([], get(initialValues, path)) + /** + * @note If the initial value as an empty array, concatenating it above + * results in [undefined]. Filter out undefined values. + */ + .filter(Boolean) + + logger.log('all foreign entries:', initialForeignRecords) + + if (this.options.unique) { + // Check if the foreign record isn't associated with another owner. + const foreignRelations = initialForeignRecords.flatMap( + (foreignRecord) => { + return this.getRelationsToOwner(foreignRecord) + }, + ) + + const isUnique = foreignRelations.every( + (relation) => relation.foreignKeys.size === 0, + ) + + invariant.as( + RelationError.for( + RelationErrorCodes.FORBIDDEN_UNIQUE_CREATE, + this.#createErrorDetails(), + ), + isUnique, + `Failed to create a unique relation at "%s": the foreign record is already associated with another owner`, + serializedPath, + ) + + // Check if another owner isn't associated with the foreign record. + const otherOwnersAssociatedWithForeignRecord = + this.#getOtherOwnerForRecords(initialForeignRecords) + + invariant.as( + RelationError.for( + RelationErrorCodes.FORBIDDEN_UNIQUE_CREATE, + this.#createErrorDetails(), + ), + otherOwnersAssociatedWithForeignRecord == null, + 'Failed to create a unique relation at "%s": the foreign record is already associated with another owner', + serializedPath, + ) + } + + for (const foreignRecord of initialForeignRecords) { + const foreignKey = foreignRecord[kPrimaryKey] + + invariant.as( + RelationError.for( + RelationErrorCodes.INVALID_FOREIGN_RECORD, + this.#createErrorDetails(), + ), + foreignKey != null, + 'Failed to store foreign record reference for "%s" relation: the referenced record (%j) is missing the primary key', + serializedPath, + foreignRecord, + ) + + if (foreignKey != null) { + this.foreignKeys.add(foreignKey) + } + } + + logger.log('updated foreign keys:', this.foreignKeys) + unset(record, path) + } + + // Define a getter that resolves foreign entries based on their ids. + // Specific relation classes implement the `resolve` method to + // return the appropriate values (i.e one/many). + definePropertyAtPath(record, path, { + enumerable: true, + configurable: true, + get: () => { + const returnValue = this.resolve(this.foreignKeys) + logger.log( + `resolving "${serializedPath}" for`, + record, + 'result:', + returnValue, + ) + + if (returnValue !== undefined) { + return returnValue + } + + /** + * @note If the relational key is present in initial values + * and is null, that means it's a nullable relation. Allow nulls. + */ + if (initialValue === null) { + return null + } + + return this.getDefaultValue() + }, + set: () => { + throw new RelationError( + `Failed to set property "${serializedPath}" on collection (${this.ownerCollection[kCollectionId]}): relational properties are read-only and can only be updated via collection updates`, + RelationErrorCodes.UNEXPECTED_SET_EXPRESSION, + this.#createErrorDetails(), + ) + }, + }) + + logger.log(`defined getter over "${serializedPath}"!`) + } + + /** + * Returns a list of relations from the given foreign record + * to the owner collection. Takes `role` into account. + */ + public getRelationsToOwner(foreignRecord: RecordType): Array { + const result: Array = [] + const isSelfReferencing = this.foreignCollections.some( + (foreignCollection) => { + return ( + foreignCollection[kCollectionId] === + this.ownerCollection[kCollectionId] + ) + }, + ) + const ownPath = this.path.join('.') + + for (const [serializedPath, relation] of foreignRecord[kRelationMap]) { + /** + * @note For self-referencing relations, the relation at the same path + * on the foreign record is not the inverse — it's the same logical + * relation pointing in the same direction. Skip it so we only return + * the actual inverse relation (a different path with the same role). + */ + if (isSelfReferencing && serializedPath === ownPath) { + continue + } + + if ( + relation.foreignCollections.some((foreignCollection) => { + return ( + foreignCollection[kCollectionId] === + this.ownerCollection[kCollectionId] + ) + }) && + relation.options.role === this.options.role + ) { + result.push(relation) + } + } + + return result + } + + #getOtherOwnerForRecords( + foreignRecords: Array, + ): RecordType | undefined { + const serializedPath = this.path.join('.') + + return this.ownerCollection.findFirst((q) => { + return q.where((otherOwner) => { + const otherOwnerRelations = otherOwner[kRelationMap] + const otherOwnerRelation = otherOwnerRelations.get(serializedPath) + + // Forego any other relation comparisons since the same collection + // shares the relation definition at the same property path. + return foreignRecords.some((foreignRecord) => { + return otherOwnerRelation.foreignKeys.has(foreignRecord[kPrimaryKey]) + }) + }) + }) + } + + #createErrorDetails(): RelationErrorDetails { + return { + path: this.path, + ownerCollection: this.ownerCollection, + foreignCollections: this.foreignCollections, + options: this.options, + } + } +} + +class One extends Relation { + public resolve(foreignKeys: Set): unknown { + if (foreignKeys.size === 0) { + return + } + + for (const foreignCollection of this.foreignCollections) { + const record = foreignCollection.findFirst((q) => + q.where((record) => { + return record[kPrimaryKey] === foreignKeys.values().next().value + }), + ) + + /** + * @note `null` is a valid value for nullable relations. + */ + if (record !== undefined) { + return record + } + } + } + + public getDefaultValue(): unknown { + return undefined + } +} + +export class Many extends Relation { + public resolve(foreignKeys: Set): unknown { + if (foreignKeys.size === 0) { + return + } + + return this.foreignCollections.flatMap((foreignCollection) => { + return foreignCollection.findMany((q) => + q.where((record) => { + return foreignKeys.has(record[kPrimaryKey]) + }), + ) + }) + } + + public getDefaultValue(): unknown { + return [] + } +} diff --git a/src/relations/Relation.ts b/src/relations/Relation.ts deleted file mode 100644 index 291c9089..00000000 --- a/src/relations/Relation.ts +++ /dev/null @@ -1,416 +0,0 @@ -import { debug } from 'debug' -import set from 'lodash/set' -import get from 'lodash/get' -import { invariant } from 'outvariant' -import { Database } from '../db/Database' -import { - Entity, - ENTITY_TYPE, - KeyType, - ModelDictionary, - PrimaryKeyType, - PRIMARY_KEY, - Value, -} from '../glossary' -import { executeQuery } from '../query/executeQuery' -import { QuerySelectorWhere } from '../query/queryTypes' -import { definePropertyAtPath } from '../utils/definePropertyAtPath' -import { findPrimaryKey } from '../utils/findPrimaryKey' -import { first } from '../utils/first' - -const log = debug('relation') - -export enum RelationKind { - OneOf = 'ONE_OF', - ManyOf = 'MANY_OF', -} - -export interface RelationAttributes { - nullable: boolean - unique: boolean -} - -export interface RelationSource { - modelName: string - primaryKey: PrimaryKeyType - propertyPath: string[] -} - -export interface RelationDefinition< - Kind extends RelationKind, - ModelName extends KeyType, - Attributes extends Partial, -> { - to: ModelName - kind: Kind - attributes?: Attributes -} - -export type LazyRelation< - Kind extends RelationKind, - ModelName extends KeyType, - Dictionary extends ModelDictionary, -> = ( - modelName: ModelName, - propertyPath: string, - dictionary: Dictionary, - db: Database, -) => Relation - -export type OneOf< - ModelName extends KeyType, - Nullable extends boolean = false, -> = Relation - -export type ManyOf< - ModelName extends KeyType, - Nullable extends boolean = false, -> = Relation - -export type RelationsList = Array<{ - propertyPath: string[] - relation: Relation -}> - -const DEFAULT_RELATION_ATTRIBUTES: RelationAttributes = { - nullable: false, - unique: false, -} - -export class Relation< - Kind extends RelationKind, - ModelName extends KeyType, - Dictionary extends ModelDictionary, - Attributes extends Partial, - ReferenceType = Kind extends RelationKind.OneOf - ? Value - : Value[], -> { - public kind: Kind - public attributes: RelationAttributes - public source: RelationSource = null as any - public target: { - modelName: string - primaryKey: PrimaryKeyType - } - - // These lazy properties are set after calling the ".apply()" method. - private dictionary: Dictionary = null as any - private db: Database = null as any - - constructor(definition: RelationDefinition) { - this.kind = definition.kind - this.attributes = { - ...DEFAULT_RELATION_ATTRIBUTES, - ...(definition.attributes || {}), - } - this.target = { - modelName: definition.to.toString(), - // @ts-expect-error Null is an intermediate value. - primaryKey: null, - } - - log( - 'constructing a "%s" relation to "%s" with attributes: %o', - this.kind, - definition.to, - this.attributes, - ) - } - - /** - * Applies the relation to the given entity. - * Creates a connection between the relation's target and source. - * Does not define the proxy property getter. - */ - public apply( - entity: Entity, - propertyPath: string[], - dictionary: Dictionary, - db: Database, - ) { - this.dictionary = dictionary - this.db = db - - const sourceModelName = entity[ENTITY_TYPE] - const sourcePrimaryKey = entity[PRIMARY_KEY] - - this.source = { - modelName: sourceModelName, - propertyPath, - primaryKey: sourcePrimaryKey, - } - - // Get the referenced model's primary key name. - const targetPrimaryKey = findPrimaryKey( - this.dictionary[this.target.modelName], - ) - - invariant( - targetPrimaryKey, - 'Failed to create a "%s" relation to "%s": referenced model does not exist or has no primary key.', - this.kind, - this.target.modelName, - ) - this.target.primaryKey = targetPrimaryKey - } - - /** - * Updates the relation references (values) to resolve the relation with. - */ - public resolveWith( - entity: Entity, - refs: ReferenceType | null, - ): void { - const exception = ( - predicate: unknown, - reason: string, - ...positionals: any[] - ): void => { - invariant( - predicate, - `Failed to resolve a "%s" relationship to "%s" at "%s.%s" (%s: "%s"): ${reason}`, - this.kind, - this.target.modelName, - this.source.modelName, - this.source.propertyPath, - this.source.primaryKey, - entity[this.source.primaryKey], - ...positionals, - ) - } - - invariant( - this.source, - 'Failed to resolve a "%s" relational property to "%s": relation has not been applied (source: %s).', - this.kind, - this.target.modelName, - this.source, - ) - - log( - 'resolving a "%s" relational property to "%s" on "%s.%s" ("%s"):', - this.kind, - this.target.modelName, - this.source.modelName, - this.source.propertyPath, - entity[this.source.primaryKey], - refs, - ) - log('entity of this relation:', entity) - - // Support null as the next relation value for nullable relations. - if (refs === null) { - exception( - this.attributes.nullable, - 'cannot resolve a non-nullable relationship with null.', - ) - - log('this relation resolves with null') - - // Override the relational property of the entity to return null. - this.setValueResolver(entity, () => { - return null - }) - - return - } - - exception( - this.target.primaryKey, - 'referenced model has no primary key set.', - ) - - const referencesList = ([] as Value[]).concat(refs) - const records = this.db.getModel(this.target.modelName) - - log('records in the referenced model:', records.keys()) - - // Forbid referencing entities from a model different than the one - // defined in the - referencesList.forEach((ref) => { - const refModelName = ref[ENTITY_TYPE as unknown as string] - const refPrimaryKey = ref[PRIMARY_KEY as unknown as string] - const refId = ref[this.target.primaryKey] - - exception( - refModelName, - 'expected a referenced entity to be "%s" but got %o', - this.target.modelName, - ref, - ) - - exception( - refModelName === this.target.modelName, - 'expected a referenced entity to be "%s" but got "%s" (%s: "%s").', - this.target.modelName, - refModelName, - refPrimaryKey, - ref[refPrimaryKey], - ) - - // Forbid referencing non-existing entities. - // This guards against assigning a compatible plain object - // as the relational value. - exception( - records.has(refId), - 'referenced entity "%s" (%s: "%s") does not exist.', - refModelName, - this.target.primaryKey, - refId, - ) - }) - - // Ensure that unique relations don't reference - // entities that are already referenced by other entities. - if (this.attributes.unique) { - log( - 'validating a unique "%s" relation to "%s" on "%s.%s"...', - this.kind, - this.target.modelName, - this.source.modelName, - this.source.propertyPath, - ) - - // Get the list of entities of the same entity type - // that reference the same relational values. - const extraneousEntities = executeQuery( - this.source.modelName, - this.source.primaryKey, - { - where: set>( - { - // Omit the current entity when querying - // the list of other entities that reference - // the same value. - [this.source.primaryKey]: { - notEquals: entity[this.source.primaryKey], - }, - }, - this.source.propertyPath, - { - [this.target.primaryKey]: { - in: referencesList.map((entity) => { - return entity[this.target.primaryKey] - }), - }, - }, - ), - }, - this.db, - ) - - log( - 'found other %s referencing the same %s:', - this.source.modelName, - this.target.modelName, - extraneousEntities, - ) - - if (extraneousEntities.length > 0) { - const extraneousReferences = extraneousEntities.flatMap( - (extraneous) => { - const references = ([] as Entity[]).concat( - get(extraneous, this.source.propertyPath), - ) - return references.map( - (entity) => entity[this.target.primaryKey], - ) - }, - ) - - const firstInvalidReference = referencesList.find((entity) => { - return extraneousReferences.includes(entity[this.target.primaryKey]) - }) - - exception( - false, - 'the referenced "%s" (%s: "%s") belongs to another "%s" (%s: "%s").', - this.target.modelName, - this.target.primaryKey, - firstInvalidReference?.[this.target.primaryKey], - this.source.modelName, - extraneousEntities[0]?.[PRIMARY_KEY], - extraneousEntities[0]?.[this.source.primaryKey], - ) - } - } - - this.setValueResolver(entity, () => { - const queryResult = referencesList.reduce[]>( - (result, ref) => { - return result.concat( - executeQuery( - this.target.modelName, - this.target.primaryKey, - { - where: { - [this.target.primaryKey]: { - equals: ref[this.target.primaryKey], - }, - }, - }, - this.db, - ), - ) - }, - [], - ) - - return this.kind === RelationKind.OneOf ? first(queryResult) : queryResult - }) - } - - private setValueResolver( - entity: Entity, - resolver: () => unknown, - ): void { - log( - 'setting value resolver at "%s" on: %j', - this.source.propertyPath, - entity, - ) - - invariant( - entity[ENTITY_TYPE], - 'Failed to set a value resolver on a "%s" relationship to "%s" at "%s.%s": provided object (%j) is not an entity.', - this.kind, - this.target.modelName, - this.source.modelName, - this.source.propertyPath.join('.'), - entity, - ) - - definePropertyAtPath(entity, this.source.propertyPath, { - // Mark the property as enumerable so it gets listed - // when iterating over the entity's properties. - enumerable: true, - // Mark the property as configurable so it could be re-defined - // when updating it during the entity update ("update"/"updateMany"). - configurable: true, - get: () => { - log( - 'GET "%s.%s" on "%s" ("%s")', - this.source.modelName, - this.source.propertyPath, - this.source.modelName, - entity[this.source.primaryKey], - this, - ) - - const nextValue = resolver() - - log( - 'resolved "%s" relation at "%s.%s" ("%s") to:', - this.kind, - this.source.modelName, - this.source.propertyPath, - entity[this.source.primaryKey], - nextValue, - ) - - return nextValue - }, - }) - } -} diff --git a/src/relations/manyOf.ts b/src/relations/manyOf.ts deleted file mode 100644 index cc007edf..00000000 --- a/src/relations/manyOf.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ManyOf, Relation, RelationAttributes, RelationKind } from './Relation' - -export function manyOf( - to: ModelName, - attributes?: Partial, -): ManyOf { - return new Relation({ - to, - kind: RelationKind.ManyOf, - attributes, - }) -} diff --git a/src/relations/oneOf.ts b/src/relations/oneOf.ts deleted file mode 100644 index c72516bf..00000000 --- a/src/relations/oneOf.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { OneOf, Relation, RelationAttributes, RelationKind } from './Relation' - -export function oneOf( - to: ModelName, - attributes?: Partial, -): OneOf { - return new Relation({ - to, - kind: RelationKind.OneOf, - attributes, - }) -} diff --git a/src/sort.ts b/src/sort.ts new file mode 100644 index 00000000..c12eef7a --- /dev/null +++ b/src/sort.ts @@ -0,0 +1,59 @@ +import type { StandardSchemaV1 } from '@standard-schema/spec' +import { get } from 'es-toolkit/compat' +import { toDeepEntries, type PropertyPath } from '#/src/utils.js' + +export type SortDirection = 'asc' | 'desc' + +export interface SortOptions { + orderBy?: OrderBy +} + +type OrderBy = + | OrderByCriteria + | Array> + +type OrderByCriteria< + Schema extends StandardSchemaV1, + T = StandardSchemaV1.InferOutput, +> = + NonNullable extends Array + ? OrderByCriteria + : NonNullable extends Record + ? { + [K in keyof T]?: OrderByCriteria + } + : SortDirection + +export function sortResults( + sortOptions: SortOptions, + data: Array>, +): void { + if (sortOptions.orderBy == null) { + return + } + + const criteria: Array<[PropertyPath, SortDirection]> = Array.isArray( + sortOptions.orderBy, + ) + ? sortOptions.orderBy.flatMap((entry) => { + return toDeepEntries(entry as any) + }) + : toDeepEntries(sortOptions.orderBy as any) + + data.sort((left, right) => { + for (const [path, sortDirection] of criteria) { + const leftValue = get(left, path) + const rightValue = get(right, path) + + if (leftValue > rightValue) { + return sortDirection === 'asc' ? 1 : -1 + } + + if (leftValue < rightValue) { + return sortDirection === 'asc' ? -1 : 1 + } + } + + return 0 + }) +} diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 00000000..860857ec --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,90 @@ +import { invariant } from 'outvariant' +import { isPlainObject } from 'es-toolkit' +import { kPrimaryKey, type RecordType } from '#/src/collection.js' + +/** + * Checks if the given value is a plain object. + */ +export function isObject(value: unknown): value is Record { + return isPlainObject(value) +} + +/** + * Checks if the given value is a record object. + */ +export function isRecord(value: unknown): value is RecordType { + /** + * @note Have a loose object check, allowing arrays, since records + * might be root-level arrays. + */ + return typeof value === 'object' && value != null && kPrimaryKey in value +} + +export function definePropertyAtPath( + target: Record, + path: Array, + descriptor: PropertyDescriptor, +): void { + let deepTarget = target + const lastKey = path[path.length - 1] + + invariant( + lastKey != null, + 'Failed to define a property at path "%j": expected the path to have at least one item', + path, + ) + + for (const key of path.slice(0, -1)) { + invariant( + typeof deepTarget[key] === 'object', + 'Failed to define property at path "%j": part "%s" is not an object', + path, + key, + ) + deepTarget = deepTarget[key] + } + + Object.defineProperty(deepTarget, lastKey, descriptor) +} + +export type PropertyPath = Array + +export function toDeepEntries( + source: Record, + entryPredicate: (value: unknown, path: PropertyPath) => boolean = () => true, + parentPath: PropertyPath = [], +): Array<[PropertyPath, V]> { + return Reflect.ownKeys(source).flatMap((key) => { + const value = source[key] + const path = parentPath.concat(key) + + if (entryPredicate(value, path)) { + if (isObject(value)) { + return toDeepEntries(value, entryPredicate, path) + } + } + + return [[path, value]] + }) +} + +export function cloneWithInternals( + value: T, + predicate: (args: { + key: string | symbol + descriptor: PropertyDescriptor + }) => boolean, +): T { + const clone = structuredClone(value) + const descriptors = Object.getOwnPropertyDescriptors(value) + + for (const key of Reflect.ownKeys(descriptors)) { + const descriptor = descriptors[key as keyof typeof descriptors] + + if (predicate({ key, descriptor }) ?? true) { + Object.defineProperty(clone, key, descriptor) + } + } + + return clone +} diff --git a/src/utils/capitalize.ts b/src/utils/capitalize.ts deleted file mode 100644 index 97831bb4..00000000 --- a/src/utils/capitalize.ts +++ /dev/null @@ -1,4 +0,0 @@ -export function capitalize(str: string): string { - const [firstLetter, ...rest] = str - return firstLetter.toUpperCase() + rest.join('') -} diff --git a/src/utils/definePropertyAtPath.ts b/src/utils/definePropertyAtPath.ts deleted file mode 100644 index a31d5a8e..00000000 --- a/src/utils/definePropertyAtPath.ts +++ /dev/null @@ -1,28 +0,0 @@ -import has from 'lodash/has' -import set from 'lodash/set' -import get from 'lodash/get' - -/** - * Abstraction over `Object.defineProperty` that supports - * property paths (nested properties). - * - * @example - * const target = {} - * definePropertyAtPath(target, 'a.b.c', { get(): { return 2 }}) - * console.log(target.a.b.c) // 2 - */ -export function definePropertyAtPath( - target: Record, - propertyPath: string[], - attributes: AttributesType, -) { - const propertyName = propertyPath[propertyPath.length - 1] - const parentPath = propertyPath.slice(0, -1) - - if (parentPath.length && !has(target, parentPath)) { - set(target, parentPath, {}) - } - - const parent = parentPath.length ? get(target, parentPath) : target - Object.defineProperty(parent, propertyName, attributes) -} diff --git a/src/utils/findPrimaryKey.ts b/src/utils/findPrimaryKey.ts deleted file mode 100644 index 3ad0c403..00000000 --- a/src/utils/findPrimaryKey.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { ModelDefinition, PrimaryKeyType } from '../glossary' -import { PrimaryKey } from '../primaryKey' - -/** - * Returns a primary key property name of the given model definition. - */ -export function findPrimaryKey( - definition: ModelDefinition, -): PrimaryKeyType | undefined { - for (const propertyName in definition) { - const value = definition[propertyName] - - if (value instanceof PrimaryKey) { - return propertyName - } - } -} diff --git a/src/utils/first.ts b/src/utils/first.ts deleted file mode 100644 index edd075e7..00000000 --- a/src/utils/first.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * Return the first element in the given array. - */ -export function first( - arr: ArrayType, -): ArrayType extends Array ? ValueType | null : never { - return arr != null && arr.length > 0 ? arr[0] : null -} diff --git a/src/utils/identity.ts b/src/utils/identity.ts deleted file mode 100644 index 46e66489..00000000 --- a/src/utils/identity.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function identity(value: T): () => T { - return () => value -} diff --git a/src/utils/inheritInternalProperties.ts b/src/utils/inheritInternalProperties.ts deleted file mode 100644 index 7bae6132..00000000 --- a/src/utils/inheritInternalProperties.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { invariant } from 'outvariant' -import { ENTITY_TYPE, PRIMARY_KEY, Entity } from '../glossary' - -export function inheritInternalProperties( - target: Record, - source: Entity, -): void { - const entityType = source[ENTITY_TYPE] - const primaryKey = source[PRIMARY_KEY] - - invariant( - entityType, - 'Failed to inherit internal properties from (%j) to (%j): provided source entity has no entity type specified.', - source, - target, - ) - invariant( - primaryKey, - 'Failed to inherit internal properties from (%j) to (%j): provided source entity has no primary key specified.', - source, - target, - ) - - Object.defineProperties(target, { - [ENTITY_TYPE]: { - enumerable: true, - value: entityType, - }, - [PRIMARY_KEY]: { - enumerable: true, - value: primaryKey, - }, - }) -} diff --git a/src/utils/isModelValueType.ts b/src/utils/isModelValueType.ts deleted file mode 100644 index 705ff035..00000000 --- a/src/utils/isModelValueType.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { ModelValueType, PrimitiveValueType } from '../glossary' - -function isPrimitiveValueType(value: any): value is PrimitiveValueType { - return ( - typeof value === 'string' || - typeof value === 'number' || - typeof value === 'boolean' || - value?.constructor?.name === 'Date' - ) -} - -export function isModelValueType(value: any): value is ModelValueType { - return isPrimitiveValueType(value) || Array.isArray(value) -} diff --git a/src/utils/isObject.ts b/src/utils/isObject.ts deleted file mode 100644 index 248ff731..00000000 --- a/src/utils/isObject.ts +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Returns true if the given value is a plain Object. - */ -export function isObject>( - value: any, -): value is O { - return ( - value != null && - typeof value === 'object' && - !Array.isArray(value) && - !(value instanceof Date) - ) -} diff --git a/src/utils/iteratorUtils.ts b/src/utils/iteratorUtils.ts deleted file mode 100644 index 212d60a7..00000000 --- a/src/utils/iteratorUtils.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { PrimaryKeyType } from "../glossary" - -export function forEach( - fn: (key: K, value: V) => any, - map: Map, -): void { - for (const [key, value] of map.entries()) { - fn(key, value) - } -} - -export function filter( - predicate: (key: K, value: V) => boolean, - map: Map, -): Map { - const nextMap = new Map() - - forEach((key, value) => { - if (predicate(key, value)) { - nextMap.set(key, value) - } - }, map) - - return nextMap -} diff --git a/src/utils/numberInRange.ts b/src/utils/numberInRange.ts deleted file mode 100644 index 35e83f8e..00000000 --- a/src/utils/numberInRange.ts +++ /dev/null @@ -1,7 +0,0 @@ -export function numberInRange( - min: number, - max: number, - actual: number, -): boolean { - return actual >= min && actual <= max -} diff --git a/src/utils/safeStringify.ts b/src/utils/safeStringify.ts deleted file mode 100644 index 2328a915..00000000 --- a/src/utils/safeStringify.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ENTITY_TYPE, PRIMARY_KEY } from '../glossary' - -export function safeStringify(value: unknown): string { - const seen = new WeakSet() - - return JSON.stringify(value, (_, value) => { - if (typeof value !== 'object' || value === null) { - return value - } - - if (seen.has(value)) { - const type = value[ENTITY_TYPE] - const primaryKey = value[PRIMARY_KEY] - - return type && primaryKey - ? `Entity(type: ${type}, ${primaryKey}: ${value[primaryKey]})` - : '[Circular Reference]' - } - - seen.add(value) - return value - }) -} diff --git a/src/utils/spread.ts b/src/utils/spread.ts deleted file mode 100644 index d26ef706..00000000 --- a/src/utils/spread.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { isObject } from './isObject' - -/** - * Clones the given object, preserving its setters/getters. - */ -export function spread< - ObjectType extends Record, ->(source: ObjectType): ObjectType { - const target = {} as ObjectType - const descriptors = Object.getOwnPropertyDescriptors(source) - - for (const [propertyName, descriptor] of Object.entries(descriptors)) { - // Spread nested objects, preserving their descriptors. - if (isObject(descriptor.value)) { - Object.defineProperty(target, propertyName, { - ...descriptor, - value: spread(descriptor.value), - }) - continue - } - - Object.defineProperty(target, propertyName, descriptor) - } - - return target -} diff --git a/test/comparators/boolean-comparators.test.ts b/test/comparators/boolean-comparators.test.ts deleted file mode 100644 index 84dd2325..00000000 --- a/test/comparators/boolean-comparators.test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { booleanComparators } from '../../src/comparators/boolean' - -test('equals', () => { - expect(booleanComparators.equals(true, true)).toBe(true) - expect(booleanComparators.equals(false, false)).toBe(true) - expect(booleanComparators.equals(true, false)).toBe(false) - expect(booleanComparators.equals(false, true)).toBe(false) -}) - -test('notEquals', () => { - expect(booleanComparators.notEquals(true, false)).toBe(true) - expect(booleanComparators.notEquals(false, true)).toBe(true) - expect(booleanComparators.notEquals(true, true)).toBe(false) - expect(booleanComparators.notEquals(false, false)).toBe(false) -}) diff --git a/test/comparators/date-comparators.test.ts b/test/comparators/date-comparators.test.ts deleted file mode 100644 index 2a1a1105..00000000 --- a/test/comparators/date-comparators.test.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { dateComparators } from '../../src/comparators/date' - -test('equals', () => { - expect( - dateComparators.equals(new Date('1980-12-10'), new Date('1980-12-10')), - ).toBe(true) - - expect( - dateComparators.equals(new Date('1980-12-10'), new Date('1980-01-01')), - ).toBe(false) -}) - -test('notEquals', () => { - expect( - dateComparators.notEquals(new Date('1980-12-10'), new Date('1980-01-01')), - ).toBe(true) - - expect( - dateComparators.notEquals(new Date('1980-12-10'), new Date('1980-12-10')), - ).toBe(false) -}) - -test('gt', () => { - expect( - dateComparators.gt(new Date('1980-01-01'), new Date('1980-06-24')), - ).toBe(true) - - expect( - dateComparators.gt(new Date('1980-02-14'), new Date('1980-02-12')), - ).toBe(false) -}) - -test('gte', () => { - expect( - dateComparators.gte(new Date('1980-01-01'), new Date('1980-06-24')), - ).toBe(true) - expect( - dateComparators.gte(new Date('1980-01-01'), new Date('1980-01-01')), - ).toBe(true) - - expect( - dateComparators.gte(new Date('1980-02-14'), new Date('1980-02-12')), - ).toBe(false) -}) - -test('lt', () => { - expect( - dateComparators.lt(new Date('1980-02-14'), new Date('1980-02-12')), - ).toBe(true) - - expect( - dateComparators.lt(new Date('1980-01-01'), new Date('1980-06-24')), - ).toBe(false) -}) - -test('lte', () => { - expect( - dateComparators.lte(new Date('1980-02-14'), new Date('1980-02-12')), - ).toBe(true) - expect( - dateComparators.lte(new Date('1980-01-01'), new Date('1980-01-01')), - ).toBe(true) - - expect( - dateComparators.lte(new Date('1980-01-01'), new Date('1980-06-24')), - ).toBe(false) -}) diff --git a/test/comparators/number-comparators.test.ts b/test/comparators/number-comparators.test.ts deleted file mode 100644 index 310564d9..00000000 --- a/test/comparators/number-comparators.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { numberComparators } from '../../src/comparators/number' - -test('equals', () => { - expect(numberComparators.equals(1, 1)).toEqual(true) - expect(numberComparators.equals(234, 234)).toEqual(true) - expect(numberComparators.equals(2, 5)).toEqual(false) -}) - -test('notEquals', () => { - expect(numberComparators.notEquals(2, 5)).toEqual(true) - expect(numberComparators.notEquals(0, 10)).toEqual(true) - expect(numberComparators.notEquals(1, 1)).toEqual(false) -}) - -test('between', () => { - expect(numberComparators.between([5, 10], 7)).toEqual(true) - expect(numberComparators.between([5, 10], 5)).toEqual(true) - expect(numberComparators.between([5, 10], 7)).toEqual(true) - expect(numberComparators.between([5, 10], 24)).toEqual(false) -}) - -test('notBetween', () => { - expect(numberComparators.notBetween([5, 10], 4)).toEqual(true) - expect(numberComparators.notBetween([5, 10], 11)).toEqual(true) - expect(numberComparators.notBetween([5, 10], 5)).toEqual(false) - expect(numberComparators.notBetween([5, 10], 10)).toEqual(false) -}) - -test('gt', () => { - expect(numberComparators.gt(2, 5)).toEqual(true) - expect(numberComparators.gt(9, 20)).toEqual(true) - expect(numberComparators.gt(20, 20)).toEqual(false) -}) - -test('gte', () => { - expect(numberComparators.gte(2, 5)).toEqual(true) - expect(numberComparators.gte(9, 20)).toEqual(true) - expect(numberComparators.gte(20, 20)).toEqual(true) - expect(numberComparators.gte(4, 2)).toEqual(false) -}) - -test('gt', () => { - expect(numberComparators.lt(5, 2)).toEqual(true) - expect(numberComparators.lt(20, 9)).toEqual(true) - expect(numberComparators.lt(20, 20)).toEqual(false) - expect(numberComparators.lt(5, 20)).toEqual(false) -}) - -test('lte', () => { - expect(numberComparators.lte(5, 2)).toEqual(true) - expect(numberComparators.lte(20, 9)).toEqual(true) - expect(numberComparators.lte(20, 20)).toEqual(true) - expect(numberComparators.lte(5, 20)).toEqual(false) -}) - -test('in', () => { - expect(numberComparators.in([5], 5)).toEqual(true) - expect(numberComparators.in([5, 10], 5)).toEqual(true) - expect(numberComparators.in([1, 3, 5], 3)).toEqual(true) - - expect(numberComparators.in([5], 3)).toEqual(false) - expect(numberComparators.in([3, 5], 4)).toEqual(false) -}) - -test('notIn', () => { - expect(numberComparators.notIn([5], 2)).toEqual(true) - expect(numberComparators.notIn([5, 10], 7)).toEqual(true) - expect(numberComparators.notIn([1, 3, 5], 4)).toEqual(true) - - expect(numberComparators.notIn([5], 5)).toEqual(false) - expect(numberComparators.notIn([3, 5], 3)).toEqual(false) -}) diff --git a/test/comparators/string-comparators.test.ts b/test/comparators/string-comparators.test.ts deleted file mode 100644 index 371f0110..00000000 --- a/test/comparators/string-comparators.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { stringComparators } from '../../src/comparators/string' - -test('equals', () => { - expect(stringComparators.equals('foo', 'foo')).toBe(true) - expect(stringComparators.equals('foo', 'bar')).toBe(false) -}) - -test('notEquals', () => { - expect(stringComparators.notEquals('foo', 'bar')).toBe(true) - expect(stringComparators.notEquals('foo', 'foo')).toBe(false) -}) - -test('contains', () => { - expect(stringComparators.contains('foo', 'footer')).toBe(true) - expect(stringComparators.contains('bar', 'abarthe')).toBe(true) - expect(stringComparators.contains('foo', 'nope')).toBe(false) -}) - -test('notContains', () => { - expect(stringComparators.notContains('foo', 'nope')).toBe(true) - expect(stringComparators.notContains('nope', 'foo')).toBe(true) - expect(stringComparators.notContains('foo', 'footer')).toBe(false) -}) - -test('in', () => { - expect(stringComparators.in(['a', 'foo'], 'a')).toBe(true) - expect(stringComparators.in(['a', 'foo'], 'foo')).toBe(true) - expect(stringComparators.in(['a', 'foo'], 'antler')).toBe(false) - expect(stringComparators.in(['a', 'foo'], 'footer')).toBe(false) -}) - -test('notIn', () => { - expect(stringComparators.notIn(['a', 'foo'], 'bar')).toBe(true) - expect(stringComparators.notIn(['a', 'foo'], 'footer')).toBe(true) - expect(stringComparators.notIn(['a', 'foo'], 'antler')).toBe(true) - expect(stringComparators.notIn(['a', 'foo'], 'a')).toBe(false) - expect(stringComparators.notIn(['a', 'foo'], 'foo')).toBe(false) -}) diff --git a/test/db/drop.test.ts b/test/db/drop.test.ts deleted file mode 100644 index 6ceb1853..00000000 --- a/test/db/drop.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { drop, factory, identity, primaryKey, oneOf } from '@mswjs/data' - -test('drops all records in the database', () => { - const db = factory({ - user: { - id: primaryKey(identity('abc-123')), - }, - }) - - db.user.create() - expect(db.user.getAll()).toHaveLength(1) - - drop(db) - expect(db.user.getAll()).toHaveLength(0) -}) - -test('does nothing when the database is already empty', () => { - const db = factory({ - user: { - id: primaryKey(identity('abc-123')), - }, - }) - - db.user.create() - db.user.delete({ - where: { - id: { - equals: 'abc-123', - }, - }, - }) - - expect(db.user.getAll()).toHaveLength(0) - - drop(db) - expect(db.user.getAll()).toHaveLength(0) -}) - -test('properly cleans up relational properties', () => { - const db = factory({ - user: { - id: primaryKey(identity('abc-123')), - }, - group: { - id: primaryKey(identity('def-456')), - owner: oneOf('user'), - }, - }) - - const user = db.user.create() - db.group.create({ owner: user }) - - expect(db.user.getAll()).toHaveLength(1) - expect(db.group.getAll()).toHaveLength(1) - - drop(db) - expect(db.user.getAll()).toHaveLength(0) - expect(db.group.getAll()).toHaveLength(0) -}) diff --git a/test/db/events.test.ts b/test/db/events.test.ts deleted file mode 100644 index 4e649e14..00000000 --- a/test/db/events.test.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { - Database, - SerializedEntity, - SERIALIZED_INTERNAL_PROPERTIES_KEY, -} from '../../src/db/Database' -import { createModel } from '../../src/model/createModel' -import { primaryKey } from '../../src/primaryKey' -import { parseModelDefinition } from '../../src/model/parseModelDefinition' -import { ENTITY_TYPE, PRIMARY_KEY } from '../../src/glossary' - -test('emits the "create" event when a new entity is created', (done) => { - const dictionary = { - user: { - id: primaryKey(String), - }, - } - - const db = new Database({ - user: dictionary.user, - }) - - db.events.on('create', (id, [modelName, entity, primaryKey]) => { - expect(id).toEqual(db.id) - expect(modelName).toEqual('user') - expect(entity).toEqual({ - /** - * @note Entity reference in the database event listener - * contains its serialized internal properties. - * This allows for this listener to re-create the entity - * when the data is transferred over other channels - * (i.e. via "BroadcastChannel" which strips object symbols). - */ - [SERIALIZED_INTERNAL_PROPERTIES_KEY]: { - entityType: 'user', - primaryKey: 'id', - }, - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'abc-123', - } as SerializedEntity) - expect(primaryKey).toBeUndefined() - done() - }) - - db.create( - 'user', - createModel( - 'user', - dictionary.user, - dictionary, - parseModelDefinition(dictionary, 'user', dictionary.user), - { - id: 'abc-123', - }, - db, - ), - ) -}) - -test('emits the "update" event when an existing entity is updated', (done) => { - const dictionary = { - user: { - id: primaryKey(String), - firstName: String, - }, - } - - const db = new Database({ - user: dictionary.user, - }) - - db.events.on('update', (id, [modelName, prevEntity, nextEntity]) => { - expect(id).toEqual(db.id) - expect(modelName).toEqual('user') - expect(prevEntity).toEqual({ - [SERIALIZED_INTERNAL_PROPERTIES_KEY]: { - entityType: 'user', - primaryKey: 'id', - }, - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'abc-123', - firstName: 'John', - } as SerializedEntity) - - expect(nextEntity).toEqual({ - [SERIALIZED_INTERNAL_PROPERTIES_KEY]: { - entityType: 'user', - primaryKey: 'id', - }, - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'def-456', - firstName: 'Kate', - } as SerializedEntity) - done() - }) - - db.create( - 'user', - createModel( - 'user', - dictionary.user, - dictionary, - parseModelDefinition(dictionary, 'user', dictionary.user), - { id: 'abc-123', firstName: 'John' }, - db, - ), - ) - db.update( - 'user', - db.getModel('user').get('abc-123')!, - createModel( - 'user', - dictionary.user, - dictionary, - parseModelDefinition(dictionary, 'user', dictionary.user), - { id: 'def-456', firstName: 'Kate' }, - db, - ), - ) -}) - -test('emits the "delete" event when an existing entity is deleted', (done) => { - const dictionary = { - user: { - id: primaryKey(String), - firstName: String, - }, - } - - const db = new Database({ - user: dictionary.user, - }) - - db.events.on('delete', (id, [modelName, primaryKey]) => { - expect(id).toEqual(db.id) - expect(modelName).toEqual('user') - expect(primaryKey).toEqual('abc-123') - done() - }) - - db.create( - 'user', - createModel( - 'user', - dictionary.user, - dictionary, - parseModelDefinition(dictionary, 'user', dictionary.user), - { id: 'abc-123', firstName: 'John' }, - db, - ), - ) - db.delete('user', 'abc-123') -}) diff --git a/test/extensions/sync.multiple.runtime.js b/test/extensions/sync.multiple.runtime.js deleted file mode 100644 index ddd96d4e..00000000 --- a/test/extensions/sync.multiple.runtime.js +++ /dev/null @@ -1,15 +0,0 @@ -import { factory, primaryKey } from '@mswjs/data' - -window.db = factory({ - user: { - id: primaryKey(String), - firstName: String, - }, -}) - -window.secondDb = factory({ - user: { - id: primaryKey(String), - firstName: String, - }, -}) diff --git a/test/extensions/sync.runtime.js b/test/extensions/sync.runtime.js deleted file mode 100644 index ddee4101..00000000 --- a/test/extensions/sync.runtime.js +++ /dev/null @@ -1,10 +0,0 @@ -import { factory, primaryKey } from '@mswjs/data' - -const db = factory({ - user: { - id: primaryKey(String), - firstName: String, - }, -}) - -window.db = db diff --git a/test/extensions/sync.test.ts b/test/extensions/sync.test.ts deleted file mode 100644 index 18a7de10..00000000 --- a/test/extensions/sync.test.ts +++ /dev/null @@ -1,207 +0,0 @@ -import * as path from 'path' -import { createBrowser, CreateBrowserApi, pageWith } from 'page-with' -import { FactoryAPI } from '../../src/glossary' - -interface User { - id: string - firstName: string -} - -declare namespace window { - export const db: FactoryAPI<{ user: User }> - export const secondDb: FactoryAPI<{ user: User }> -} - -let browser: CreateBrowserApi - -beforeAll(async () => { - browser = await createBrowser({ - serverOptions: { - webpackConfig: { - resolve: { - alias: { - '@mswjs/data': path.resolve(__dirname, '../..'), - }, - }, - }, - }, - }) -}) - -afterAll(async () => { - await browser.cleanup() -}) - -test('synchornizes entity create across multiple clients', async () => { - const runtime = await pageWith({ - example: path.resolve(__dirname, 'sync.runtime.js'), - }) - const secondPage = await runtime.context.newPage() - await secondPage.goto(runtime.origin) - await runtime.page.bringToFront() - - await runtime.page.evaluate(() => { - window.db.user.create({ - id: 'abc-123', - firstName: 'John', - }) - }) - - expect(await secondPage.evaluate(() => window.db.user.getAll())).toEqual([ - { - id: 'abc-123', - firstName: 'John', - }, - ]) -}) - -test('synchornizes entity update across multiple clients', async () => { - const runtime = await pageWith({ - example: path.resolve(__dirname, 'sync.runtime.js'), - }) - const secondPage = await runtime.context.newPage() - await secondPage.goto(runtime.origin) - await runtime.page.bringToFront() - - await runtime.page.evaluate(() => { - window.db.user.create({ - id: 'abc-123', - firstName: 'John', - }) - }) - - await secondPage.evaluate(() => { - return window.db.user.update({ - where: { - id: { - equals: 'abc-123', - }, - }, - data: { - firstName: 'Kate', - }, - }) - }) - - const expectedUsers = [ - { - id: 'abc-123', - firstName: 'Kate', - }, - ] - expect(await secondPage.evaluate(() => window.db.user.getAll())).toEqual( - expectedUsers, - ) - expect(await runtime.page.evaluate(() => window.db.user.getAll())).toEqual( - expectedUsers, - ) -}) - -test('synchronizes entity delete across multiple clients', async () => { - const runtime = await pageWith({ - example: path.resolve(__dirname, 'sync.runtime.js'), - }) - const secondPage = await runtime.context.newPage() - await secondPage.goto(runtime.origin) - await runtime.page.bringToFront() - - await runtime.page.evaluate(() => { - window.db.user.create({ - id: 'abc-123', - firstName: 'John', - }) - }) - - await secondPage.evaluate(() => { - window.db.user.delete({ - where: { - id: { - equals: 'abc-123', - }, - }, - }) - }) - - expect(await secondPage.evaluate(() => window.db.user.getAll())).toEqual([]) - expect(await runtime.page.evaluate(() => window.db.user.getAll())).toEqual([]) -}) - -test('handles events from multiple database instances separately', async () => { - const runtime = await pageWith({ - example: path.resolve(__dirname, 'sync.multiple.runtime.js'), - }) - const secondPage = await runtime.context.newPage() - await secondPage.goto(runtime.origin) - await runtime.page.bringToFront() - - const john = { - id: 'abc-123', - firstName: 'John', - } - - const kate = { - id: 'def-456', - firstName: 'Kate', - } - - // Create a new user in the first database. - await runtime.page.evaluate(() => { - window.db.user.create({ id: 'abc-123', firstName: 'John' }) - }) - expect(await runtime.page.evaluate(() => window.db.user.getAll())).toEqual([ - john, - ]) - expect(await secondPage.evaluate(() => window.db.user.getAll())).toEqual([ - john, - ]) - - // No entities are created in the second, unrelated database. - expect( - await secondPage.evaluate(() => { - return window.secondDb.user.getAll() - }), - ).toEqual([]) - - await secondPage.evaluate(() => { - window.secondDb.user.create({ id: 'def-456', firstName: 'Kate' }) - }) - - // A new entity created in a different database is synchronized in another client. - expect( - await runtime.page.evaluate(() => window.secondDb.user.getAll()), - ).toEqual([kate]) - - // An unrelated database does not contain a newly created entity. - expect(await runtime.page.evaluate(() => window.db.user.getAll())).toEqual([ - john, - ]) -}) - -test('handles events from multiple databases on different hostnames', async () => { - const firstRuntime = await pageWith({ - example: path.resolve(__dirname, 'sync.runtime.js'), - }) - const secondRuntime = await pageWith({ - example: path.resolve(__dirname, 'sync.multiple.runtime.js'), - }) - expect(firstRuntime.origin).not.toEqual(secondRuntime.origin) - - await firstRuntime.page.evaluate(() => { - window.db.user.create({ id: 'abc-123', firstName: 'John' }) - }) - expect( - await secondRuntime.page.evaluate(() => window.db.user.getAll()), - ).toEqual([]) - - await secondRuntime.page.evaluate(() => { - window.db.user.create({ id: 'def-456', firstName: 'Kate' }) - }) - expect( - await firstRuntime.page.evaluate(() => window.db.user.getAll()), - ).toEqual([ - { - id: 'abc-123', - firstName: 'John', - }, - ]) -}) diff --git a/test/jest.config.ts b/test/jest.config.ts deleted file mode 100644 index 25f2e91c..00000000 --- a/test/jest.config.ts +++ /dev/null @@ -1,8 +0,0 @@ -export default { - preset: 'ts-jest', - testTimeout: 60000, - moduleNameMapper: { - '^@mswjs/data(.*)': '/../src', - }, - setupFilesAfterEnv: ['./jest.setup.ts'], -} diff --git a/test/jest.d.ts b/test/jest.d.ts deleted file mode 100644 index 974e0808..00000000 --- a/test/jest.d.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { Value } from 'lib/glossary' - -type OwnMatcherFn< - Target extends unknown, - Matcher extends (...args: any[]) => void, -> = (...args: Parameters) => void - -export interface OwnMatchers extends Record> { - toHaveRelationalProperty: OwnMatcherFn< - Value, - (propertyName: string, value?: Value | null) => void - > -} - -type CustomExtendMap = { - [MatcherName in keyof OwnMatchers]: OwnMatchers[MatcherName] extends OwnMatcherFn< - infer TargetType, - any - > - ? ( - this: jest.MatcherContext, - received: TargetType, - ...actual: Parameters - ) => ReturnType - : never -} - -declare global { - namespace jest { - interface Matchers extends OwnMatchers {} - interface ExpectExtendMap extends CustomExtendMap {} - } -} diff --git a/test/jest.setup.ts b/test/jest.setup.ts deleted file mode 100644 index ade2956d..00000000 --- a/test/jest.setup.ts +++ /dev/null @@ -1,24 +0,0 @@ -expect.extend({ - toHaveRelationalProperty(entity, propertyName, value) { - expect(entity).toHaveProperty(propertyName) - - // Relational property must only have a getter. - const descriptor = Object.getOwnPropertyDescriptor(entity, propertyName)! - expect(descriptor.get).toBeInstanceOf(Function) - expect(descriptor.value).not.toBeDefined() - expect(descriptor.enumerable).toEqual(true) - expect(descriptor.configurable).toEqual(true) - - if (value) { - const actualValue = entity[propertyName] - expect(actualValue).toEqual(value) - } - - return { - pass: true, - message() { - return '' - }, - } - }, -}) diff --git a/test/model/count.test.ts b/test/model/count.test.ts deleted file mode 100644 index 75affc28..00000000 --- a/test/model/count.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { datatype } from 'faker' -import { factory, primaryKey } from '../../src' -import { repeat } from '../testUtils' - -test('counts the amount of records for the model', () => { - const db = factory({ - book: { - id: primaryKey(datatype.uuid), - }, - }) - repeat(db.book.create, 12) - - const booksCount = db.book.count() - expect(booksCount).toBe(12) -}) - -test('returns 0 when no records are present', () => { - const db = factory({ - book: { - id: primaryKey(datatype.uuid), - }, - user: { - id: primaryKey(datatype.uuid), - }, - }) - repeat(db.book.create, 5) - - const usersCount = db.user.count() - expect(usersCount).toBe(0) -}) - -test('counts the amount of records that match the query', () => { - const db = factory({ - book: { - id: primaryKey(datatype.uuid), - pagesCount: Number, - }, - }) - db.book.create({ pagesCount: 150 }) - db.book.create({ pagesCount: 335 }) - db.book.create({ pagesCount: 750 }) - - const longBooks = db.book.count({ - where: { - pagesCount: { - gte: 300, - }, - }, - }) - expect(longBooks).toBe(2) -}) - -test('returns 0 when no records match the query', () => { - const db = factory({ - book: { - id: primaryKey(datatype.uuid), - pagesCount: Number, - }, - }) - db.book.create({ pagesCount: 150 }) - db.book.create({ pagesCount: 335 }) - db.book.create({ pagesCount: 750 }) - - const longBooks = db.book.count({ - where: { - pagesCount: { - gte: 1000, - }, - }, - }) - expect(longBooks).toBe(0) -}) diff --git a/test/model/create.test-d.ts b/test/model/create.test-d.ts deleted file mode 100644 index 605e0612..00000000 --- a/test/model/create.test-d.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { factory, primaryKey, nullable } from '../../src' -import faker from 'faker' - -const db = factory({ - user: { - id: primaryKey(String), - firstName: String, - lastName: nullable(faker.name.lastName), - age: nullable(() => null), - address: { - billing: { - country: String, - }, - }, - }, - company: { - name: primaryKey(String), - }, -}) - -// @ts-expect-error Unknown model name. -db.unknownModel.create() - -db.user.create({ - id: 'abc-123', - // @ts-expect-error Unknown model property. - unknownProp: true, - address: { - billing: { - country: 'us', - }, - }, -}) - -db.user.create({ - // @ts-expect-error Non-nullable properties cannot be instantiated with null. - firstName: null, -}) - -db.user.create({ - address: { - // @ts-expect-error Property "unknown" does not exist on "user.address". - unknown: 'value', - }, -}) - -db.user.create({ - address: { - billing: { - // @ts-expect-error Property "unknown" does not exist on "user.address.billing". - unknown: 'value', - }, - }, -}) - -db.user.create({ - // @ts-expect-error Relational properties must reference - // a valid entity of that model. - country: 'Exact string', -}) - -db.user.create({ - // @ts-expect-error Relational property must reference - // the exact model type ("country"). - country: db.post.create(), -}) - -db.user.create({ - // Any property is optional. - // When not provided, its value getter from the model - // will be executed to get the initial value. - firstName: 'John', -}) - -const user = db.user.create({ - // Nullable properties can have an initialValue of null or the property type - lastName: null, - age: 15, -}) - -// @ts-expect-error lastName property is possibly null -user.lastName.toUpperCase() - -// @ts-expect-error property 'toUpperCase' does not exist on type 'number' -user.age?.toUpperCase() diff --git a/test/model/create.test.ts b/test/model/create.test.ts deleted file mode 100644 index 3e16527b..00000000 --- a/test/model/create.test.ts +++ /dev/null @@ -1,257 +0,0 @@ -import { datatype, name } from 'faker' -import { factory, primaryKey, oneOf, manyOf, nullable } from '../../src' -import { identity } from '../../src/utils/identity' - -test('creates a new entity', () => { - const userId = datatype.uuid() - const db = factory({ - user: { - id: primaryKey(identity(userId)), - }, - }) - - // Without any arguments a new entity is seeded - // using the value getters defined in the model. - const randomUser = db.user.create() - expect(randomUser.id).toEqual(userId) -}) - -test('creates a new entity with initial values', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - }, - }) - - // Entity can be given exact values to seed. - const exactUser = db.user.create({ - id: 'abc-123', - }) - expect(exactUser.id).toEqual('abc-123') -}) - -test('creates a new entity with an array property', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - favouriteNumbers: Array, - }, - }) - - const exactUser = db.user.create({ - id: 'abc-123', - favouriteNumbers: [1, 2, 3], - }) - expect(exactUser.id).toEqual('abc-123') - expect(exactUser.favouriteNumbers).toEqual([1, 2, 3]) -}) - -test('creates a new entity with an array property with array of objects assigned', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - notes: Array, - }, - }) - - const exampleNotes = [ - { - key: '001', - value: 'Buy groceries', - }, - { - key: '002', - value: 'Call grandpa on Friday', - }, - ] - - const exactUser = db.user.create({ - id: 'abc-123', - notes: exampleNotes, - }) - - expect(exactUser.id).toEqual('abc-123') - expect(exactUser.notes).toEqual(exampleNotes) -}) - -test('creates a new entity with nullable properties', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - name: nullable(name.findName), - age: nullable(() => null), - address: { - street: String, - number: nullable(() => null), - }, - }, - }) - - const user = db.user.create({ - id: 'abc-123', - name: null, - }) - - expect(user.name).toEqual(null) - expect(user.age).toEqual(null) - expect(user.address.number).toEqual(null) -}) - -test('supports nested objects in the model definition', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - name: String, - info: { - firstName: String, - lastName: String, - address: { - street: () => 'Yellow Brick Road', - number: () => 1, - }, - tags: Array, - }, - }, - }) - - // Entity can be given exact values to seed. - const exactUser = db.user.create({ - id: 'abc-123', - name: 'sampleUser', - info: { - firstName: 'Reginald', - lastName: 'Dwight', - address: { - number: 73, - }, - tags: ['one', 'two'], - }, - }) - - expect(exactUser.id).toEqual('abc-123') - expect(exactUser.name).toEqual('sampleUser') - expect(exactUser.info).toEqual({ - firstName: 'Reginald', - lastName: 'Dwight', - address: { - street: 'Yellow Brick Road', - number: 73, - }, - tags: ['one', 'two'], - }) -}) - -test('relational properties can be declared in nested objects', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - name: String, - info: { - country: oneOf('country'), - firstName: String, - lastName: String, - }, - }, - country: { - id: primaryKey(datatype.uuid), - name: String, - }, - }) - - const japan = db.country.create({ - name: 'Japan', - }) - - const exactUser = db.user.create({ - name: 'user', - info: { - country: japan, - firstName: 'Ryuichi', - lastName: 'Sakamoto', - }, - }) - - expect(exactUser.name).toEqual('user') - expect(exactUser.info.firstName).toEqual('Ryuichi') - expect(exactUser.info.lastName).toEqual('Sakamoto') - expect(exactUser.info.country).toEqual( - expect.objectContaining({ name: 'Japan' }), - ) -}) - -test('uses value getters when creating an entity with nested arrays', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - name: String, - info: { - tags: () => [1, 2], - documents: () => [], - }, - }, - }) - - const exactUser = db.user.create({ - id: 'abc-123', - name: 'sampleUser', - }) - - expect(exactUser.name).toEqual('sampleUser') - expect(exactUser).toHaveProperty('info') - expect(exactUser.info.tags).toEqual([1, 2]) - expect(exactUser.info.documents).toEqual([]) -}) - -test('supports property names with dots in model definition', () => { - const db = factory({ - user: { - 'employee.id': primaryKey(datatype.uuid), - }, - }) - - const user = db.user.create({ - 'employee.id': 'abc-123', - }) - - expect(user['employee.id']).toEqual('abc-123') -}) - -test('throws an exception when null used as initial value for non-nullable properties', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - name: String, - }, - }) - - expect(() => { - db.user.create({ - // @ts-expect-error Cannot use null as the initial value for a non-nullable property. - name: null, - }) - }).toThrowError( - 'Failed to create a "user" entity: a non-nullable property "name" cannot be instantiated with null. Use the "nullable" function when defining this property to support nullable value.', - ) -}) - -test('throws an exception when null used as initial value for non-nullable relations', () => { - const db = factory({ - user: { - id: primaryKey(String), - posts: manyOf('post'), - }, - post: { - id: primaryKey(String), - }, - }) - - expect(() => { - db.user.create({ - id: 'user-1', - // @ts-expect-error Cannot use null as the initial value for a non-nullable relation. - posts: null, - }) - }).toThrowError( - 'Failed to define a "MANY_OF" relationship to "post" at "user.posts" (id: "user-1"): cannot set a non-nullable relationship to null.', - ) -}) diff --git a/test/model/delete.test-d.ts b/test/model/delete.test-d.ts deleted file mode 100644 index 425d5288..00000000 --- a/test/model/delete.test-d.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { factory, oneOf, primaryKey } from '../../src' - -const db = factory({ - user: { - id: primaryKey(String), - firstName: String, - age: Number, - createdAt: () => new Date(), - country: oneOf('country'), - address: { - billing: { - country: String, - }, - }, - }, - country: { - code: primaryKey(String), - }, -}) - -db.user.delete({ - // Provide no query to match all entities. - where: {}, -}) - -db.user.delete({ - where: { - id: { - equals: 'abc-123', - // @ts-expect-error Only string comparators are allowed. - gte: 2, - }, - firstName: { - contains: 'John', - }, - age: { - gte: 18, - // @ts-expect-error Only number comparators are allowed. - contains: 'value', - }, - createdAt: { - gte: new Date('2004-01-01'), - }, - }, -}) - -// Delete by a nested property value. -db.user.delete({ - where: { - address: { - billing: { - country: { - equals: 'us', - }, - }, - }, - }, -}) - -db.user.delete({ - where: { - address: { - // @ts-expect-error Property "unknown" does not exist on "user.address". - unknown: 'value', - }, - }, -}) - -db.user.delete({ - where: { - address: { - billing: { - // @ts-expect-error Property "unknown" does not exist on "user.address.billing". - unknown: 'value', - }, - }, - }, -}) - -// Delete by a relational property value. -db.user.delete({ - where: { - country: { - code: { - equals: 'us', - }, - }, - }, -}) - -db.user.delete({ - where: { - // @ts-expect-error Property "unknown" doesn't exist on "user". - unknown: { - equals: 'abc-123', - }, - }, -}) - -db.user.delete({ - where: { - firstName: { - // @ts-expect-error Unknown value comparator. - unknownComparator: '123', - }, - }, -}) diff --git a/test/model/delete.test.ts b/test/model/delete.test.ts deleted file mode 100644 index 549bc8bf..00000000 --- a/test/model/delete.test.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { datatype, name } from 'faker' -import { factory, primaryKey } from '../../src' -import { OperationErrorType } from '../../src/errors/OperationError' -import { getThrownError } from '../testUtils' - -test('deletes a unique entity that matches the query', () => { - const userId = datatype.uuid() - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: name.findName, - }, - }) - - db.user.create({ firstName: 'Kate' }) - db.user.create({ id: userId, firstName: 'John' }) - db.user.create({ firstName: 'Alice' }) - - const deletedUser = db.user.delete({ - where: { - id: { - equals: userId, - }, - }, - }) - expect(deletedUser).toHaveProperty('id', userId) - expect(deletedUser).toHaveProperty('firstName', 'John') - - const remainingUsers = db.user.getAll() - const remainingUserNames = remainingUsers.map((user) => user.firstName) - expect(remainingUserNames).toEqual(['Kate', 'Alice']) -}) - -test('deletes the first entity that matches the query', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: name.firstName, - followersCount: Number, - }, - }) - - db.user.create({ firstName: 'John', followersCount: 10 }) - db.user.create({ firstName: 'Kate', followersCount: 12 }) - db.user.create({ firstName: 'Alice', followersCount: 15 }) - - const deletedUser = db.user.delete({ - where: { - followersCount: { - gt: 10, - }, - }, - }) - expect(deletedUser).toHaveProperty('firstName', 'Kate') - - const deletedUserSearch = db.user.findFirst({ - where: { - firstName: { - equals: 'Kate', - }, - }, - }) - expect(deletedUserSearch).toBeNull() - - const allUsers = db.user.getAll() - const userNames = allUsers.map((user) => user.firstName) - expect(userNames).toEqual(['John', 'Alice']) -}) - -test('throws an exception when no entities matches the query in strict mode', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - }, - }) - db.user.create() - db.user.create() - - const error = getThrownError(() => { - db.user.delete({ - where: { - id: { - equals: 'abc-123', - }, - }, - strict: true, - }) - }) - - expect(error).toHaveProperty('name', 'OperationError') - expect(error).toHaveProperty('type', OperationErrorType.EntityNotFound) - expect(error).toHaveProperty( - 'message', - 'Failed to execute "delete" on the "user" model: no entity found matching the query "{"id":{"equals":"abc-123"}}".', - ) -}) - -test('does nothing when no entity matches the query', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: name.firstName, - }, - }) - db.user.create({ firstName: 'Kate' }) - db.user.create({ firstName: 'Alice' }) - db.user.create({ firstName: 'John' }) - - const deletedUser = db.user.delete({ - where: { - id: { - equals: 'abc-123', - }, - }, - }) - expect(deletedUser).toBeNull() - - const allUsers = db.user.getAll() - expect(allUsers).toHaveLength(3) - - const userNames = allUsers.map((user) => user.firstName) - expect(userNames).toEqual(['Kate', 'Alice', 'John']) -}) diff --git a/test/model/deleteMany.test-d.ts b/test/model/deleteMany.test-d.ts deleted file mode 100644 index 8479dc32..00000000 --- a/test/model/deleteMany.test-d.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { factory, primaryKey, oneOf } from '../../src' - -const db = factory({ - user: { - id: primaryKey(String), - firstName: String, - age: Number, - createdAt: () => new Date(), - country: oneOf('country'), - address: { - billing: { - country: String, - }, - }, - }, - country: { - code: primaryKey(String), - }, -}) - -db.user.deleteMany({ - // Providing no query criteria matches all entities. - where: {}, -}) - -db.user.deleteMany({ - where: { - id: { - equals: 'abc-123', - }, - firstName: { - contains: 'John', - }, - age: { - gte: 18, - }, - createdAt: { - gte: new Date('2004-01-01'), - }, - }, -}) - -// Delete multiple entities by a nested property value. -db.user.deleteMany({ - where: { - address: { - billing: { - country: { - equals: 'us', - }, - }, - }, - }, -}) - -db.user.deleteMany({ - where: { - address: { - // @ts-expect-error Property "unknown" does not exist on "user.address". - unknown: 'value', - }, - }, -}) - -db.user.deleteMany({ - where: { - address: { - billing: { - // @ts-expect-error Property "unknown" does not exist on "user.address.billing". - unknown: 'value', - }, - }, - }, -}) - -// Delete multiple entities by their relational property value. -db.user.deleteMany({ - where: { - country: { - code: { - equals: 'us', - }, - }, - }, -}) - -db.user.deleteMany({ - where: { - // @ts-expect-error Property "unknown" doesn't exist on "user". - unknown: { - equals: 'abc-123', - }, - }, -}) - -db.user.deleteMany({ - where: { - firstName: { - // @ts-expect-error Unknown value comparator. - unknownComparator: '123', - }, - }, -}) diff --git a/test/model/deleteMany.test.ts b/test/model/deleteMany.test.ts deleted file mode 100644 index fc3e7d16..00000000 --- a/test/model/deleteMany.test.ts +++ /dev/null @@ -1,120 +0,0 @@ -import { datatype, name } from 'faker' -import { factory, primaryKey } from '../../src' -import { OperationErrorType } from '../../src/errors/OperationError' -import { getThrownError } from '../testUtils' - -test('deletes all entites that match the query', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: name.firstName, - followersCount: datatype.number, - }, - }) - - db.user.create({ - firstName: 'John', - followersCount: 10, - }) - db.user.create({ - firstName: 'Kate', - followersCount: 12, - }) - db.user.create({ - firstName: 'Alice', - followersCount: 18, - }) - db.user.create({ - firstName: 'Joseph', - followersCount: 24, - }) - - const deletedUsers = db.user.deleteMany({ - where: { - followersCount: { - between: [11, 20], - }, - }, - })! - expect(deletedUsers).toHaveLength(2) - - const deletedUserNames = deletedUsers.map((user) => user.firstName) - expect(deletedUserNames).toEqual(['Kate', 'Alice']) - - const queriedDeletedUsers = db.user.findMany({ - where: { - followersCount: { - between: [11, 20], - }, - }, - }) - expect(queriedDeletedUsers).toEqual([]) - - const restUsers = db.user.getAll() - expect(restUsers).toHaveLength(2) - - const restUserNames = restUsers.map((user) => user.firstName) - expect(restUserNames).toEqual(['John', 'Joseph']) -}) - -test('throws an exception when no entities match the query in a strict mode', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - }, - }) - db.user.create() - db.user.create() - - const error = getThrownError(() => { - db.user.deleteMany({ - where: { - id: { - in: ['abc-123', 'def-456'], - }, - }, - strict: true, - }) - }) - - expect(error).toHaveProperty('name', 'OperationError') - expect(error).toHaveProperty('type', OperationErrorType.EntityNotFound) - expect(error).toHaveProperty( - 'message', - 'Failed to execute "deleteMany" on the "user" model: no entities found matching the query "{"id":{"in":["abc-123","def-456"]}}".', - ) -}) - -test('does nothing when no entities match the query', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: name.firstName, - followersCount: datatype.number, - }, - }) - - db.user.create({ - firstName: 'John', - followersCount: 10, - }) - db.user.create({ - firstName: 'Kate', - followersCount: 12, - }) - - const deletedUsers = db.user.deleteMany({ - where: { - followersCount: { - gte: 1000, - }, - }, - }) - expect(deletedUsers).toBeNull() - - const restUsers = db.user.getAll() - expect(restUsers).toHaveLength(2) - - const restUserNames = restUsers.map((user) => user.firstName) - expect(restUserNames).toEqual(['John', 'Kate']) -}) diff --git a/test/model/findFirst.test-d.ts b/test/model/findFirst.test-d.ts deleted file mode 100644 index 71bb6514..00000000 --- a/test/model/findFirst.test-d.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { factory, oneOf, primaryKey } from '../../src' - -const db = factory({ - user: { - id: primaryKey(String), - firstName: String, - createdAt: () => new Date(), - country: oneOf('country'), - }, - country: { - id: primaryKey(String), - name: String, - }, - post: { - id: primaryKey(String), - title: String, - }, -}) - -db.user.findFirst({ - where: { - // @ts-expect-error Unknown model property. - unknown: { - equals: 2, - }, - }, -}) - -db.user.findFirst({ - where: { - id: { - equals: 'abc-123', - // @ts-expect-error Only string comparators are allowed. - gte: 2, - }, - }, -}) diff --git a/test/model/findFirst.test.ts b/test/model/findFirst.test.ts deleted file mode 100644 index 045ceb34..00000000 --- a/test/model/findFirst.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { datatype } from 'faker' -import { factory, primaryKey } from '../../src' -import { OperationErrorType } from '../../src/errors/OperationError' -import { identity } from '../../src/utils/identity' -import { getThrownError } from '../testUtils' - -test('returns the only matching entity', () => { - const userId = datatype.uuid() - const db = factory({ - user: { - id: primaryKey(identity(userId)), - }, - }) - - db.user.create() - - const user = db.user.findFirst({ - where: { - id: { - equals: userId, - }, - }, - }) - expect(user).toHaveProperty('id', userId) -}) - -test('returns the first entity among multiple matching entities', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - followersCount: Number, - }, - }) - - db.user.create({ followersCount: 10 }) - db.user.create({ followersCount: 12 }) - db.user.create({ followersCount: 15 }) - - const user = db.user.findFirst({ - where: { - followersCount: { - gt: 10, - }, - }, - }) - expect(user).toHaveProperty('followersCount', 12) -}) - -test('throws an exception when no results in strict mode', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - }, - }) - db.user.create() - - const error = getThrownError(() => { - db.user.findFirst({ - where: { - id: { - equals: 'abc-123', - }, - }, - strict: true, - }) - }) - - expect(error).toHaveProperty('name', 'OperationError') - expect(error).toHaveProperty('type', OperationErrorType.EntityNotFound) - expect(error).toHaveProperty( - 'message', - `Failed to execute "findFirst" on the "user" model: no entity found matching the query "{"id":{"equals":"abc-123"}}".`, - ) -}) - -test('returns null when found no matching entities', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - }, - }) - db.user.create() - - const user = db.user.findFirst({ - where: { - id: { - equals: 'abc-123', - }, - }, - }) - expect(user).toBeNull() -}) diff --git a/test/model/findMany.test-d.ts b/test/model/findMany.test-d.ts deleted file mode 100644 index e5e959e8..00000000 --- a/test/model/findMany.test-d.ts +++ /dev/null @@ -1,217 +0,0 @@ -import { factory, oneOf, primaryKey } from '../../src' - -const db = factory({ - user: { - id: primaryKey(String), - firstName: String, - age: Number, - createdAt: () => new Date(), - country: oneOf('country'), - address: { - billing: { - street: String, - code: String, - }, - }, - }, - country: { - code: primaryKey(String), - }, -}) - -db.user.findMany({ - where: { - id: { - equals: 'abc-123', - }, - firstName: { - contains: 'John', - }, - createdAt: { - gte: new Date('2020-01-01'), - }, - country: { - code: { - equals: 'us', - }, - }, - }, -}) - -db.user.findMany({ - where: { - address: { - billing: { - code: { - equals: 'us', - }, - }, - }, - }, -}) - -db.user.findMany({ - where: { - address: { - // @ts-expect-error Property "unknown" doesn't exist on "user.address". - unknown: {}, - }, - }, -}) - -db.user.findMany({ - where: { - address: { - billing: { - // @ts-expect-error Property "unknown" doesn't exist on "user.address". - unknown: {}, - }, - }, - }, -}) - -db.user.findMany({ - where: { - // @ts-expect-error Unknown model property. - unknown: { - equals: 2, - }, - }, -}) - -db.user.findMany({ - where: { - id: { - equals: 'abc-123', - // @ts-expect-error Only string comparators are allowed. - gte: 2, - }, - }, -}) - -/** - * Sorting. - */ -// Single-criteria sort by a primitive value. -db.user.findMany({ - orderBy: { - id: 'asc', - }, -}) - -db.user.findMany({ - orderBy: { - // @ts-expect-error Unknown property name. - unknown: 'asc', - }, -}) - -db.user.findMany({ - // @ts-expect-error Unknown sort direction. - orderBy: { - id: 'any', - }, -}) - -// Single-criteria sort by a nested value. -db.user.findMany({ - orderBy: { - address: { - billing: { - code: 'desc', - }, - }, - }, -}) - -db.user.findMany({ - orderBy: { - address: { - // @ts-expect-error Unknown property name. - unknown: 'asc', - }, - }, -}) - -db.user.findMany({ - // @ts-expect-error Unknown property name "billing.unknown". - orderBy: { - address: { - billing: { - unknown: 'asc', - }, - }, - }, -}) - -// Single-criteria sort by a relational value. -db.user.findMany({ - orderBy: { - country: { - code: 'asc', - }, - }, -}) - -// Multi-criteria sort by primitive values. -db.user.findMany({ - orderBy: [ - { - id: 'asc', - }, - { - age: 'desc', - }, - ], -}) - -// Multi-criteria sort by nested values. -db.user.findMany({ - orderBy: [ - { - address: { - billing: { - street: 'asc', - }, - }, - }, - { - address: { - billing: { - code: 'desc', - }, - }, - }, - ], -}) - -// One key restriction. -db.user.findMany({ - // @ts-expect-error Cannot specify multiple order keys. - orderBy: { - id: 'asc', - age: 'desc', - }, -}) - -db.user.findMany({ - // @ts-expect-error Cannot specify multiple order keys. - orderBy: { - address: { - billing: { - code: 'asc', - street: 'desc', - }, - }, - }, -}) - -db.user.findMany({ - orderBy: [ - // @ts-expect-error Cannot specify multiple order keys. - { - id: 'asc', - age: 'desc', - }, - ], -}) diff --git a/test/model/findMany.test.ts b/test/model/findMany.test.ts deleted file mode 100644 index 5597222c..00000000 --- a/test/model/findMany.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { datatype } from 'faker' -import { factory, primaryKey } from '../../src' -import { OperationErrorType } from '../../src/errors/OperationError' -import { getThrownError } from '../testUtils' - -test('returns all matching entities', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - followersCount: Number, - }, - }) - - db.user.create({ followersCount: 10 }) - db.user.create({ followersCount: 12 }) - db.user.create({ followersCount: 15 }) - - const users = db.user.findMany({ - where: { - followersCount: { - gt: 10, - }, - }, - }) - expect(users).toHaveLength(2) - const usersFollowersCount = users.map((user) => user.followersCount) - expect(usersFollowersCount).toEqual([12, 15]) -}) - -test('throws an exception when no results in strict mode', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - }, - }) - db.user.create() - db.user.create() - - const error = getThrownError(() => { - db.user.findMany({ - where: { - id: { - in: ['abc-123', 'def-456'], - }, - }, - strict: true, - }) - }) - - expect(error).toHaveProperty('name', 'OperationError') - expect(error).toHaveProperty('type', OperationErrorType.EntityNotFound) - expect(error).toHaveProperty( - 'message', - 'Failed to execute "findMany" on the "user" model: no entities found matching the query "{"id":{"in":["abc-123","def-456"]}}".', - ) -}) - -test('returns an empty array when not found matching entities', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - followersCount: Number, - }, - }) - - db.user.create({ followersCount: 10 }) - db.user.create({ followersCount: 12 }) - db.user.create({ followersCount: 15 }) - - const users = db.user.findMany({ - where: { - followersCount: { - gte: 1000, - }, - }, - }) - expect(users).toEqual([]) -}) diff --git a/test/model/getAll.test-d.ts b/test/model/getAll.test-d.ts deleted file mode 100644 index db403c17..00000000 --- a/test/model/getAll.test-d.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { factory, manyOf, primaryKey } from '../../src' - -const db = factory({ - user: { - id: primaryKey(String), - firstName: String, - address: { - billing: { - country: String, - }, - }, - posts: manyOf('post'), - }, - post: { - id: primaryKey(String), - title: String, - }, -}) - -const allUsers = db.user.getAll() - -allUsers[0].id -allUsers[0].firstName -allUsers[0].address.billing?.country - -// Relational properties. -const user = allUsers[0] -const { posts = [] } = user -posts[0].id -posts[0].title - -// @ts-expect-error Property "unknown" doesn't exist on "post". -posts[0].unknown - -// @ts-expect-error Property "unknown" doesn't exist on "user". -allUsers[0].unknown diff --git a/test/model/getAll.test.ts b/test/model/getAll.test.ts deleted file mode 100644 index a0229c32..00000000 --- a/test/model/getAll.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { datatype } from 'faker' -import { factory, primaryKey } from '../../src' - -test('returns all entities', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: String, - }, - }) - - db.user.create({ firstName: 'John' }) - db.user.create({ firstName: 'Kate' }) - db.user.create({ firstName: 'Alice' }) - - const allUsers = db.user.getAll() - expect(allUsers).toHaveLength(3) - - const userNames = allUsers.map((user) => user.firstName) - expect(userNames).toEqual(['John', 'Kate', 'Alice']) -}) - -test('returns an empty list when found no entities', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: String, - }, - }) - - const allUsers = db.user.getAll() - expect(allUsers).toEqual([]) -}) diff --git a/test/model/relationalProperties.test-d.ts b/test/model/relationalProperties.test-d.ts deleted file mode 100644 index 1fe10ac0..00000000 --- a/test/model/relationalProperties.test-d.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { factory, manyOf, oneOf, primaryKey, nullable } from '@mswjs/data' - -const db = factory({ - user: { - id: primaryKey(String), - posts: manyOf('post'), - }, - post: { - id: primaryKey(String), - text: String, - author: oneOf('user'), - reply: nullable(oneOf('post')), - likedBy: nullable(manyOf('user')), - }, -}) - -const user = db.user.create() -const post = db.post.create() - -// @ts-expect-error author is potentially undefined -post.author.id - -// @ts-expect-error reply is potentially null -post.reply.id - -// @ts-expect-error likedBy is potentially null -post.likedBy.length - -// nullable oneOf relationships are not potentially undefined, only null -if (post.reply !== null) { - // we can call reply.text.toUpperCase after excluding null from types - post.reply.text.toUpperCase() -} - -// nullable manyOf relationships are not potentially undefined, only null -if (post.likedBy !== null) { - // we can call likedBy.pop after excluding null from types - post.likedBy.pop() -} diff --git a/test/model/relationalProperties.test.ts b/test/model/relationalProperties.test.ts deleted file mode 100644 index 02e01d93..00000000 --- a/test/model/relationalProperties.test.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { oneOf, primaryKey, nullable } from '../../src' -import { - Relation, - RelationKind, - RelationsList, -} from '../../src/relations/Relation' -import { defineRelationalProperties } from '../../src/model/defineRelationalProperties' -import { testFactory } from '../../test/testUtils' - -it('marks relational properties as enumerable', () => { - const { db, dictionary, databaseInstance } = testFactory({ - user: { - id: primaryKey(String), - name: String, - }, - post: { - id: primaryKey(String), - title: String, - author: oneOf('user'), - }, - }) - - const user = db.user.create({ - id: 'user-1', - name: 'John Maverick', - }) - const post = db.post.create({ - id: 'post-1', - title: 'Test Post', - }) - - const relations: RelationsList = [ - { - propertyPath: ['author'], - relation: new Relation({ - to: 'user', - kind: RelationKind.OneOf, - }), - }, - ] - - defineRelationalProperties( - post, - { - author: user, - }, - relations, - dictionary, - databaseInstance, - ) - - expect(post.propertyIsEnumerable('author')).toEqual(true) -}) - -it('marks nullable relational properties as enumerable', () => { - const { db, dictionary, databaseInstance } = testFactory({ - user: { - id: primaryKey(String), - name: String, - }, - post: { - id: primaryKey(String), - title: String, - author: nullable(oneOf('user')), - }, - }) - - const user = db.user.create({ - id: 'user-1', - name: 'John Maverick', - }) - - const post = db.post.create({ - id: 'post-1', - title: 'Test Post', - }) - - const relations: RelationsList = [ - { - propertyPath: ['author'], - relation: new Relation({ - to: 'user', - kind: RelationKind.OneOf, - }), - }, - ] - - defineRelationalProperties( - post, - { - author: user, - }, - relations, - dictionary, - databaseInstance, - ) - - expect(post.propertyIsEnumerable('author')).toEqual(true) -}) diff --git a/test/model/toGraphQLHandlers.test.ts b/test/model/toGraphQLHandlers.test.ts deleted file mode 100644 index cec0a4d4..00000000 --- a/test/model/toGraphQLHandlers.test.ts +++ /dev/null @@ -1,425 +0,0 @@ -import fetch from 'node-fetch' -import { datatype } from 'faker' -import { setupServer } from 'msw/node' -import { factory, primaryKey, drop } from '../../src' - -const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: String, - age: Number, - }, -}) - -const server = setupServer() - -beforeAll(() => { - server.listen() -}) - -afterEach(() => { - drop(db) - server.resetHandlers() -}) - -afterAll(() => { - server.close() -}) - -async function executeQuery(args: { - query: string - variables?: Record -}) { - const res = await fetch('http://localhost', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(args), - }) - - return res.json() -} - -it('supports querying all the users', async () => { - server.use(...db.user.toHandlers('graphql', 'http://localhost')) - db.user.create({ firstName: 'John' }) - db.user.create({ firstName: 'Kate' }) - db.user.create({ firstName: 'Joseph' }) - - const res = await executeQuery({ - query: ` - query GetUsers { - users { - firstName - } - } - `, - }) - - expect(res).toEqual({ - data: { - users: [ - { - firstName: 'John', - }, - { - firstName: 'Kate', - }, - { - firstName: 'Joseph', - }, - ], - }, - }) -}) - -it('supports offset pagination when querying all users', async () => { - server.use(...db.user.toHandlers('graphql', 'http://localhost')) - db.user.create({ firstName: 'John' }) - db.user.create({ firstName: 'Kate' }) - db.user.create({ firstName: 'Joseph' }) - db.user.create({ firstName: 'Eva' }) - - const res = await executeQuery({ - query: ` - query GetUsers { - users(skip: 1, take: 2) { - firstName - } - } - `, - }) - - expect(res).toEqual({ - data: { - users: [ - { - firstName: 'Kate', - }, - { - firstName: 'Joseph', - }, - ], - }, - }) -}) - -it('supports cursor pagination when querying all users', async () => { - server.use(...db.user.toHandlers('graphql', 'http://localhost')) - db.user.create({ id: 'abc-123', firstName: 'John' }) - db.user.create({ id: 'def-456', firstName: 'Kate' }) - db.user.create({ id: 'ghi-789', firstName: 'Joseph' }) - db.user.create({ id: 'xyz-321', firstName: 'Eva' }) - - const res = await executeQuery({ - query: ` - query GetUsers { - users(cursor: "ghi-789", take: 2) { - firstName - } - } - `, - }) - - expect(res).toEqual({ - data: { - users: [ - { - firstName: 'Eva', - }, - ], - }, - }) -}) - -it('supports querying all users by a field', async () => { - server.use(...db.user.toHandlers('graphql', 'http://localhost')) - db.user.create({ firstName: 'John', age: 22 }) - db.user.create({ firstName: 'Kate', age: 16 }) - db.user.create({ firstName: 'Joseph', age: 38 }) - - const res = await executeQuery({ - query: ` - query GetAdults { - users(where: { age: { gte: 18 } }) { - firstName - } - } - `, - }) - - expect(res).toEqual({ - data: { - users: [ - { - firstName: 'John', - }, - { - firstName: 'Joseph', - }, - ], - }, - }) -}) - -it('supports querying a user by the primary key', async () => { - server.use(...db.user.toHandlers('graphql', 'http://localhost')) - db.user.create({ id: 'abc-123', firstName: 'John' }) - db.user.create({ id: 'def-456', firstName: 'Kate' }) - db.user.create({ id: 'ghi-789', firstName: 'Joseph' }) - - const res = await executeQuery({ - query: ` - query GetUser($id: ID!) { - user(where: { id: { equals: $id } }) { - id - firstName - } - } - `, - variables: { - id: 'def-456', - }, - }) - - expect(res).toEqual({ - data: { - user: { - id: 'def-456', - firstName: 'Kate', - }, - }, - }) -}) - -it('supports querying a user by any field', async () => { - server.use(...db.user.toHandlers('graphql', 'http://localhost')) - db.user.create({ id: 'abc-123', firstName: 'John', age: 16 }) - db.user.create({ id: 'def-456', firstName: 'Kate', age: 17 }) - db.user.create({ id: 'ghi-789', firstName: 'Joseph', age: 22 }) - - const res = await executeQuery({ - query: ` - query GetAdult { - user(where: { age: { gte: 22 } }) { - id - firstName - } - } - `, - }) - - expect(res).toEqual({ - data: { - user: { - id: 'ghi-789', - firstName: 'Joseph', - }, - }, - }) -}) - -it('supports creating a new user', async () => { - server.use(...db.user.toHandlers('graphql', 'http://localhost')) - - const res = await executeQuery({ - query: ` - mutation CreateUser($input: UserInput!) { - createUser(data: $input) { - firstName - age - } - } - `, - variables: { - input: { - firstName: 'Kate', - age: 27, - }, - }, - }) - - expect(res).toEqual({ - data: { - createUser: { - age: 27, - firstName: 'Kate', - }, - }, - }) -}) - -it('supports updating a user', async () => { - server.use(...db.user.toHandlers('graphql', 'http://localhost')) - db.user.create({ id: 'abc-123', firstName: 'John', age: 16 }) - db.user.create({ id: 'def-456', firstName: 'Kate', age: 17 }) - - const res = await executeQuery({ - query: ` - mutation UpdateUser($input: UserInput!) { - updateUser( - where: { firstName: { equals: "Kate" } } - data: $input - ) { - firstName - age - } - } - `, - variables: { - input: { - age: 24, - }, - }, - }) - - expect(res).toEqual({ - data: { - updateUser: { - age: 24, - firstName: 'Kate', - }, - }, - }) -}) - -it('supports updating multiple users', async () => { - server.use(...db.user.toHandlers('graphql', 'http://localhost')) - db.user.create({ id: 'abc-123', firstName: 'John', age: 17 }) - db.user.create({ id: 'def-456', firstName: 'Kate', age: 24 }) - db.user.create({ id: 'ghi-789', firstName: 'Joseph', age: 14 }) - - const res = await executeQuery({ - query: ` - mutation UpdateUser($input: UserInput!) { - updateUsers( - where: { age: { lt: 18 } } - data: $input - ) { - id - firstName - age - } - } - `, - variables: { - input: { - firstName: 'Mr. Clone', - }, - }, - }) - - expect(res).toEqual({ - data: { - updateUsers: [ - { - id: 'abc-123', - age: 17, - firstName: 'Mr. Clone', - }, - { - id: 'ghi-789', - age: 14, - firstName: 'Mr. Clone', - }, - ], - }, - }) -}) - -it('supports deleting a user by the primary key', async () => { - server.use(...db.user.toHandlers('graphql', 'http://localhost')) - db.user.create({ id: 'abc-123', firstName: 'John' }) - db.user.create({ id: 'def-456', firstName: 'Kate' }) - db.user.create({ id: 'ghi-789', firstName: 'Joseph' }) - - const res = await executeQuery({ - query: ` - mutation DeleteUser { - deleteUser( - where: { id: { equals: "def-456" } } - ) { - id - firstName - } - } - `, - }) - - expect(res).toEqual({ - data: { - deleteUser: { - id: 'def-456', - firstName: 'Kate', - }, - }, - }) -}) - -it('supports deleting a user by any field', async () => { - server.use(...db.user.toHandlers('graphql', 'http://localhost')) - db.user.create({ id: 'abc-123', firstName: 'John' }) - db.user.create({ id: 'def-456', firstName: 'Kate' }) - db.user.create({ id: 'ghi-789', firstName: 'Joseph' }) - - const res = await executeQuery({ - query: ` - mutation DeleteUser { - deleteUser( - where: { firstName: { equals: "John" } } - ) { - id - firstName - } - } - `, - }) - - expect(res).toEqual({ - data: { - deleteUser: { - id: 'abc-123', - firstName: 'John', - }, - }, - }) -}) - -it('supports deleting multiple users', async () => { - server.use(...db.user.toHandlers('graphql', 'http://localhost')) - db.user.create({ id: 'abc-123', firstName: 'John', age: 17 }) - db.user.create({ id: 'def-456', firstName: 'Kate', age: 24 }) - db.user.create({ id: 'ghi-789', firstName: 'Joseph', age: 14 }) - - const res = await executeQuery({ - query: ` - mutation DeleteUsers { - deleteUsers( - where: { age: { lt: 18 } } - ) { - id - firstName - age - } - } - `, - }) - - expect(res).toEqual({ - data: { - deleteUsers: [ - { - id: 'abc-123', - firstName: 'John', - age: 17, - }, - { - id: 'ghi-789', - firstName: 'Joseph', - age: 14, - }, - ], - }, - }) -}) diff --git a/test/model/toGraphQLSchema.test.ts b/test/model/toGraphQLSchema.test.ts deleted file mode 100644 index e524fadd..00000000 --- a/test/model/toGraphQLSchema.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { datatype } from 'faker' -import { printSchema } from 'graphql' -import { factory, primaryKey } from '../../src' - -const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: String, - age: Number, - }, -}) - -test('generates a graphql schema', () => { - const schema = db.user.toGraphQLSchema() - expect(printSchema(schema)).toMatchInlineSnapshot(` - "type Query { - user(where: UserQueryInput): User - users(take: Int, skip: Int, cursor: ID, where: UserQueryInput): [User] - } - - type User { - id: ID - firstName: String - age: Int - } - - input UserQueryInput { - id: IdQueryType - firstName: StringQueryType - age: IntQueryType - } - - input IdQueryType { - equals: ID - notEquals: ID - contains: ID - notContains: ID - in: [ID] - notIn: [ID] - } - - input StringQueryType { - equals: String - notEquals: String - contains: String - notContains: String - in: [String] - notIn: [String] - } - - input IntQueryType { - equals: Int - notEquals: Int - between: [Int] - notBetween: [Int] - gt: Int - gte: Int - lt: Int - lte: Int - in: [Int] - notIn: [Int] - } - - type Mutation { - createUser(data: UserInput): User - updateUser(where: UserQueryInput, data: UserInput): User - updateUsers(where: UserQueryInput, data: UserInput): [User] - deleteUser(where: UserQueryInput): User - deleteUsers(where: UserQueryInput): [User] - } - - input UserInput { - id: ID - firstName: String - age: Int - } - " - `) -}) diff --git a/test/model/toRestHandlers/basic.test.ts b/test/model/toRestHandlers/basic.test.ts deleted file mode 100644 index 7f8cd2f7..00000000 --- a/test/model/toRestHandlers/basic.test.ts +++ /dev/null @@ -1,573 +0,0 @@ -import fetch from 'node-fetch' -import { setupServer } from 'msw/node' -import { factory, drop, primaryKey } from '@mswjs/data' - -const db = factory({ - user: { - id: primaryKey(String), - firstName: String, - lastName: String, - }, -}) - -const server = setupServer() - -beforeAll(() => { - server.listen() -}) - -afterEach(() => { - drop(db) - server.resetHandlers() -}) - -afterAll(() => { - server.close() -}) - -it('generates CRUD request handlers for the model', () => { - const userHandlers = db.user.toHandlers('rest') - const displayRoutes = userHandlers.map((handler) => handler.info.header) - - expect(displayRoutes).toEqual([ - 'GET /users', - 'GET /users/:id', - 'POST /users', - 'PUT /users/:id', - 'DELETE /users/:id', - ]) -}) - -describe('GET /users', () => { - it('handles a GET request to get all entities', async () => { - server.use(...db.user.toHandlers('rest', 'http://localhost')) - - db.user.create({ - id: 'abc-123', - firstName: 'John', - lastName: 'White', - }) - db.user.create({ - id: 'def-456', - firstName: 'Kate', - lastName: 'Moen', - }) - - const res = await fetch('http://localhost/users') - const users = await res.json() - - expect(res.status).toEqual(200) - expect(users).toEqual([ - { - id: 'abc-123', - firstName: 'John', - lastName: 'White', - }, - { - id: 'def-456', - firstName: 'Kate', - lastName: 'Moen', - }, - ]) - }) - - it('returns offset paginated entities', async () => { - server.use(...db.user.toHandlers('rest', 'http://localhost')) - - db.user.create({ - id: 'abc-123', - firstName: 'John', - lastName: 'White', - }) - db.user.create({ - id: 'def-456', - firstName: 'Kate', - lastName: 'Moen', - }) - db.user.create({ - id: 'ghi-789', - firstName: 'Joseph', - lastName: 'Sipes', - }) - db.user.create({ - id: 'xyz-321', - firstName: 'Eva', - lastName: 'Grant', - }) - - const res = await fetch('http://localhost/users?skip=1&take=2') - const users = await res.json() - - expect(users).toEqual([ - { - id: 'def-456', - firstName: 'Kate', - lastName: 'Moen', - }, - { - id: 'ghi-789', - firstName: 'Joseph', - lastName: 'Sipes', - }, - ]) - }) - - it('returns offset paginated entities without an explicit "skip" parameter', async () => { - server.use(...db.user.toHandlers('rest', 'http://localhost')) - - db.user.create({ - id: 'abc-123', - firstName: 'John', - lastName: 'White', - }) - db.user.create({ - id: 'def-456', - firstName: 'Kate', - lastName: 'Moen', - }) - db.user.create({ - id: 'ghi-789', - firstName: 'Joseph', - lastName: 'Sipes', - }) - db.user.create({ - id: 'xyz-321', - firstName: 'Eva', - lastName: 'Grant', - }) - - const res = await fetch('http://localhost/users?take=2') - const users = await res.json() - expect(users).toEqual([ - { - id: 'abc-123', - firstName: 'John', - lastName: 'White', - }, - { - id: 'def-456', - firstName: 'Kate', - lastName: 'Moen', - }, - ]) - }) - - it('returns offset paginated entities with the "skip" parameter set to 0', async () => { - server.use(...db.user.toHandlers('rest', 'http://localhost')) - - db.user.create({ - id: 'abc-123', - firstName: 'John', - lastName: 'White', - }) - db.user.create({ - id: 'def-456', - firstName: 'Kate', - lastName: 'Moen', - }) - db.user.create({ - id: 'ghi-789', - firstName: 'Joseph', - lastName: 'Sipes', - }) - db.user.create({ - id: 'xyz-321', - firstName: 'Eva', - lastName: 'Grant', - }) - - const res = await fetch('http://localhost/users?skip=0&take=2') - const users = await res.json() - expect(users).toEqual([ - { - id: 'abc-123', - firstName: 'John', - lastName: 'White', - }, - { - id: 'def-456', - firstName: 'Kate', - lastName: 'Moen', - }, - ]) - }) - - it('returns cursor paginated entities', async () => { - server.use(...db.user.toHandlers('rest', 'http://localhost')) - - db.user.create({ - id: 'abc-123', - firstName: 'John', - lastName: 'White', - }) - db.user.create({ - id: 'def-456', - firstName: 'Kate', - lastName: 'Moen', - }) - db.user.create({ - id: 'ghi-789', - firstName: 'Joseph', - lastName: 'Sipes', - }) - db.user.create({ - id: 'xyz-321', - firstName: 'Eva', - lastName: 'Grant', - }) - - const res = await fetch('http://localhost/users?cursor=def-456&take=2') - const users = await res.json() - - expect(users).toEqual([ - { - id: 'ghi-789', - firstName: 'Joseph', - lastName: 'Sipes', - }, - { - id: 'xyz-321', - firstName: 'Eva', - lastName: 'Grant', - }, - ]) - }) - - it('returns cursor paginated entities without an explicit "take" parameter', async () => { - server.use(...db.user.toHandlers('rest', 'http://localhost')) - - db.user.create({ - id: 'abc-123', - firstName: 'John', - lastName: 'White', - }) - db.user.create({ - id: 'def-456', - firstName: 'Kate', - lastName: 'Moen', - }) - db.user.create({ - id: 'ghi-789', - firstName: 'Joseph', - lastName: 'Sipes', - }) - db.user.create({ - id: 'xyz-321', - firstName: 'Eva', - lastName: 'Grant', - }) - - const res = await fetch('http://localhost/users?cursor=abc-123') - const users = await res.json() - - expect(users).toEqual([ - { - id: 'def-456', - firstName: 'Kate', - lastName: 'Moen', - }, - { - id: 'ghi-789', - firstName: 'Joseph', - lastName: 'Sipes', - }, - { - id: 'xyz-321', - firstName: 'Eva', - lastName: 'Grant', - }, - ]) - }) - - it('return filtered entities', async () => { - server.use(...db.user.toHandlers('rest', 'http://localhost')) - - db.user.create({ - id: 'abc-123', - firstName: 'John', - lastName: 'White', - }) - db.user.create({ - id: 'def-456', - firstName: 'Kate', - lastName: 'Moen', - }) - db.user.create({ - id: 'def-789', - firstName: 'Kate', - lastName: 'Hilll', - }) - const res = await fetch( - 'http://localhost/users?firstName=Kate&lastName=Moen', - ) - const users = await res.json() - - expect(users).toEqual([ - { - id: 'def-456', - firstName: 'Kate', - lastName: 'Moen', - }, - ]) - }) - - it('return all entities when wrong filter param is provided', async () => { - server.use(...db.user.toHandlers('rest', 'http://localhost')) - - db.user.create({ - id: 'abc-123', - firstName: 'John', - lastName: 'White', - }) - db.user.create({ - id: 'def-456', - firstName: 'Kate', - lastName: 'Moen', - }) - const res = await fetch('http://localhost/users?surname=Kate') - - const json = await res.json() - - expect(res.status).toEqual(400) - expect(json).toEqual({ - message: 'Failed to query the "user" model: unknown property "surname".', - }) - }) -}) - -describe('GET /users/:id', () => { - it('handles a GET request to get a single entity', async () => { - server.use(...db.user.toHandlers('rest', 'http://localhost')) - - db.user.create({ - id: 'abc-123', - firstName: 'John', - lastName: 'White', - }) - db.user.create({ - id: 'def-456', - firstName: 'Kate', - lastName: 'Moen', - }) - - const res = await fetch('http://localhost/users/def-456') - const user = await res.json() - - expect(res.status).toEqual(200) - expect(user).toEqual({ - id: 'def-456', - firstName: 'Kate', - lastName: 'Moen', - }) - }) - - it('returns a 404 response when getting a non-existing entity', async () => { - server.use(...db.user.toHandlers('rest', 'http://localhost')) - - db.user.create({ - id: 'abc-123', - firstName: 'John', - lastName: 'White', - }) - - const res = await fetch('http://localhost/users/xyz-321') - const json = await res.json() - - expect(res.status).toEqual(404) - expect(json).toEqual({ - message: - 'Failed to execute "findFirst" on the "user" model: no entity found matching the query "{"id":{"equals":"xyz-321"}}".', - }) - }) -}) - -describe('POST /users', () => { - it('handles a POST request to create a new entity', async () => { - server.use(...db.user.toHandlers('rest', 'http://localhost')) - - const res = await fetch('http://localhost/users', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - id: 'abc-123', - firstName: 'Joseph', - lastName: 'Sipes', - }), - }) - const user = await res.json() - - expect(res.status).toEqual(201) - expect(user).toEqual({ - id: 'abc-123', - firstName: 'Joseph', - lastName: 'Sipes', - }) - }) - - it('returns a 409 response when creating a user with the same id', async () => { - server.use(...db.user.toHandlers('rest', 'http://localhost')) - - db.user.create({ - id: 'abc-123', - }) - - const res = await fetch('http://localhost/users', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - id: 'abc-123', - firstName: 'Joseph', - }), - }) - const json = await res.json() - - expect(res.status).toEqual(409) - expect(json).toEqual({ - message: - 'Failed to create a "user" entity: an entity with the same primary key "abc-123" ("id") already exists.', - }) - }) -}) - -describe('PUT /users/:id', () => { - it('handles a PUT request to update an entity', async () => { - server.use(...db.user.toHandlers('rest', 'http://localhost')) - - db.user.create({ - id: 'abc-123', - firstName: 'John', - lastName: 'White', - }) - - const res = await fetch('http://localhost/users/abc-123', { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - firstName: 'Joseph', - }), - }) - const user = await res.json() - - expect(res.status).toEqual(200) - expect(user).toEqual({ - id: 'abc-123', - firstName: 'Joseph', - lastName: 'White', - }) - }) - - it('returns a 404 response when updating a non-existing entity', async () => { - server.use(...db.user.toHandlers('rest', 'http://localhost')) - - const res = await fetch('http://localhost/users/abc-123', { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - firstName: 'Joseph', - }), - }) - const json = await res.json() - - expect(res.status).toEqual(404) - expect(json).toEqual({ - message: - 'Failed to execute "update" on the "user" model: no entity found matching the query "{"id":{"equals":"abc-123"}}".', - }) - }) - - it('returns a 409 response when updating an entity with primary key of another entity', async () => { - server.use(...db.user.toHandlers('rest', 'http://localhost')) - - db.user.create({ - id: 'abc-123', - firstName: 'John', - lastName: 'White', - }) - db.user.create({ - id: 'def-456', - firstName: 'Kate', - lastName: 'Moen', - }) - - const res = await fetch('http://localhost/users/abc-123', { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - id: 'def-456', - firstName: 'Joseph', - lastName: 'Sipes', - }), - }) - const json = await res.json() - - expect(res.status).toEqual(409) - expect(json).toEqual({ - message: - 'Failed to execute "update" on the "user" model: the entity with a primary key "def-456" ("id") already exists.', - }) - }) -}) - -describe('DELETE /users/:id', () => { - it('handles a DELETE request to delete an entity', async () => { - server.use(...db.user.toHandlers('rest', 'http://localhost')) - - db.user.create({ - id: 'abc-123', - firstName: 'John', - lastName: 'White', - }) - db.user.create({ - id: 'def-456', - firstName: 'Kate', - lastName: 'Moen', - }) - - const res = await fetch('http://localhost/users/def-456', { - method: 'DELETE', - }) - const user = await res.json() - expect(res.status).toEqual(200) - expect(user).toEqual({ - id: 'def-456', - firstName: 'Kate', - lastName: 'Moen', - }) - - const allUsers = await fetch('http://localhost/users').then((res) => - res.json(), - ) - expect(allUsers).toEqual([ - { - id: 'abc-123', - firstName: 'John', - lastName: 'White', - }, - ]) - }) - - it('returns a 404 response when deleting a non-existing entity', async () => { - server.use(...db.user.toHandlers('rest', 'http://localhost')) - - const res = await fetch('http://localhost/users/def-456', { - method: 'DELETE', - }) - const json = await res.json() - - expect(res.status).toEqual(404) - expect(json).toEqual({ - message: - 'Failed to execute "delete" on the "user" model: no entity found matching the query "{"id":{"equals":"def-456"}}".', - }) - }) -}) diff --git a/test/model/toRestHandlers/primary-key-number.test.ts b/test/model/toRestHandlers/primary-key-number.test.ts deleted file mode 100644 index 7ea8872d..00000000 --- a/test/model/toRestHandlers/primary-key-number.test.ts +++ /dev/null @@ -1,210 +0,0 @@ -import fetch from 'node-fetch' -import { setupServer } from 'msw/node' -import { factory, drop, primaryKey } from '../../../src' - -const db = factory({ - todo: { - id: primaryKey(Number), - title: String, - }, -}) - -const server = setupServer() - -beforeAll(() => { - server.listen() -}) - -afterEach(() => { - drop(db) - server.resetHandlers() -}) - -afterAll(() => { - server.close() -}) - -it('generates CRUD request handlers for the model', () => { - const userHandlers = db.todo.toHandlers('rest') - const displayRoutes = userHandlers.map((handler) => handler.info.header) - - expect(displayRoutes).toEqual([ - 'GET /todos', - 'GET /todos/:id', - 'POST /todos', - 'PUT /todos/:id', - 'DELETE /todos/:id', - ]) -}) - -describe('GET /todos/:id', () => { - it('handles a GET request to get a single entity', async () => { - server.use(...db.todo.toHandlers('rest', 'http://localhost')) - - db.todo.create({ - id: 123, - title: 'Todo 1', - }) - db.todo.create({ - id: 456, - title: 'Todo 2', - }) - - const res = await fetch('http://localhost/todos/123') - const todo = await res.json() - - expect(res.status).toEqual(200) - expect(todo).toEqual({ - id: 123, - title: 'Todo 1', - }) - }) - - it('returns a 404 response when getting a non-existing entity', async () => { - server.use(...db.todo.toHandlers('rest', 'http://localhost')) - - db.todo.create({ - id: 123, - title: 'Todo 1', - }) - - const res = await fetch('http://localhost/todos/456') - const json = await res.json() - - expect(res.status).toEqual(404) - expect(json).toEqual({ - message: - 'Failed to execute "findFirst" on the "todo" model: no entity found matching the query "{"id":{"equals":456}}".', - }) - }) -}) - -describe('PUT /todos/:id', () => { - it('handles a PUT request to update an entity', async () => { - server.use(...db.todo.toHandlers('rest', 'http://localhost')) - - db.todo.create({ - id: 123, - title: 'Todo 1', - }) - - const res = await fetch('http://localhost/todos/123', { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - title: 'Todo 1 updated', - }), - }) - const todo = await res.json() - - expect(res.status).toEqual(200) - expect(todo).toEqual({ - id: 123, - title: 'Todo 1 updated', - }) - }) - - it('returns a 404 response when updating a non-existing entity', async () => { - server.use(...db.todo.toHandlers('rest', 'http://localhost')) - - const res = await fetch('http://localhost/todos/123', { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - title: 'Todo 1 updated', - }), - }) - const json = await res.json() - - expect(res.status).toEqual(404) - expect(json).toEqual({ - message: - 'Failed to execute "update" on the "todo" model: no entity found matching the query "{"id":{"equals":123}}".', - }) - }) - - it('returns a 409 response when updating an entity with primary key of another entity', async () => { - server.use(...db.todo.toHandlers('rest', 'http://localhost')) - - db.todo.create({ - id: 123, - title: 'Todo 1', - }) - db.todo.create({ - id: 456, - title: 'Todo 2', - }) - - const res = await fetch('http://localhost/todos/123', { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - id: 456, - title: 'Todo 1 updated', - }), - }) - const json = await res.json() - - expect(res.status).toEqual(409) - expect(json).toEqual({ - message: - 'Failed to execute "update" on the "todo" model: the entity with a primary key "456" ("id") already exists.', - }) - }) -}) - -describe('DELETE /todos/:id', () => { - it('handles a DELETE request to delete an entity', async () => { - server.use(...db.todo.toHandlers('rest', 'http://localhost')) - - db.todo.create({ - id: 123, - title: 'Todo 1', - }) - db.todo.create({ - id: 456, - title: 'Todo 2', - }) - - const res = await fetch('http://localhost/todos/456', { - method: 'DELETE', - }) - const todo = await res.json() - expect(res.status).toEqual(200) - expect(todo).toEqual({ - id: 456, - title: 'Todo 2', - }) - - const alltodos = await fetch('http://localhost/todos').then((res) => - res.json(), - ) - expect(alltodos).toEqual([ - { - id: 123, - title: 'Todo 1', - }, - ]) - }) - - it('returns a 404 response when deleting a non-existing entity', async () => { - server.use(...db.todo.toHandlers('rest', 'http://localhost')) - - const res = await fetch('http://localhost/todos/456', { - method: 'DELETE', - }) - const json = await res.json() - - expect(res.status).toEqual(404) - expect(json).toEqual({ - message: - 'Failed to execute "delete" on the "todo" model: no entity found matching the query "{"id":{"equals":456}}".', - }) - }) -}) diff --git a/test/model/update.test-d.ts b/test/model/update.test-d.ts deleted file mode 100644 index 3c3bf185..00000000 --- a/test/model/update.test-d.ts +++ /dev/null @@ -1,153 +0,0 @@ -import faker from 'faker' -import { factory, oneOf, manyOf, primaryKey, nullable } from '@mswjs/data' - -const db = factory({ - user: { - id: primaryKey(String), - firstName: String, - lastName: nullable(faker.name.lastName), - age: Number, - createdAt: () => new Date(), - country: oneOf('country'), - company: nullable(oneOf('company')), - address: { - billing: { - country: String, - city: nullable(() => null), - }, - }, - }, - country: { - code: primaryKey(String), - }, - company: { - name: primaryKey(String), - employees: manyOf('user'), - countries: nullable(manyOf('country')), - }, -}) - -db.user.update({ - where: { - id: { - equals: 'abc-123', - // @ts-expect-error Only string comparators are allowed. - gte: 2, - }, - firstName: { - contains: 'John', - }, - age: { - gte: 18, - // @ts-expect-error Only number comparators are allowed. - contains: 'value', - }, - createdAt: { - gte: new Date('2004-01-01'), - }, - }, - data: { - id: 'next', - firstName: 'next', - age: 24, - country: db.country.create({ code: 'de' }), - company: db.company.create({ name: 'Umbrella' }), - lastName: null, - // @ts-expect-error Unable to update non-nullable values to null - updatedAt: null, - }, -}) - -// Query and update through nested properties. -db.user.update({ - where: { - address: { - billing: { - country: { - equals: 'us', - }, - }, - }, - }, - data: { - address: { - billing: { - country: 'de', - city: 'Berlin', - }, - }, - }, -}) - -// Update nullable hasOne relations to null -db.user.update({ - where: { - id: { - equals: 'abc-123', - }, - }, - data: { - company: null, - // @ts-expect-error unable to update non-nullable relations to null - country: null, - }, -}) - -// Update nullable hasMany relations to null -db.company.update({ - where: { - name: { - equals: 'Umbrella', - }, - }, - data: { - countries: null, - // @ts-expect-error unable to update non-nullable hasMany relations to null - employees: null, - }, -}) - -db.user.update({ - where: {}, - data: { - id(id, user) { - user.firstName - // @ts-expect-error Unknown property. - user.unknown - - return id.toUpperCase() - }, - age(age) { - age.toExponential - return age + 10 - }, - }, -}) - -// Update a nested property using value getter. -db.user.update({ - where: { - address: { - billing: { - country: { - equals: 'us', - }, - }, - }, - }, - data: { - address: { - billing: { - country(country, user) { - user.firstName - user.address.billing?.country - - // @ts-expect-error Property "unknown" doesn't exist on "user". - user.unknown - - return country.toUpperCase() - }, - }, - }, - }, -}) diff --git a/test/model/update.test.ts b/test/model/update.test.ts deleted file mode 100644 index cdc34a12..00000000 --- a/test/model/update.test.ts +++ /dev/null @@ -1,595 +0,0 @@ -import { datatype, name } from 'faker' -import { factory, oneOf, primaryKey, nullable } from '../../src' -import { ENTITY_TYPE, PRIMARY_KEY } from '../../src/glossary' -import { OperationErrorType } from '../../src/errors/OperationError' -import { getThrownError } from '../testUtils' - -test('updates a unique entity that matches the query', () => { - const userId = datatype.uuid() - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: name.findName, - }, - }) - db.user.create({ - id: userId, - firstName: 'Joseph', - }) - db.user.create() - - const updatedUser = db.user.update({ - where: { - id: { - equals: userId, - }, - }, - data: { - firstName: 'John', - }, - }) - expect(updatedUser).toHaveProperty('firstName', 'John') - - const userResult = db.user.findFirst({ - where: { - id: { - equals: userId, - }, - }, - }) - expect(userResult).toHaveProperty('firstName', 'John') -}) - -test('updates a property that had no initial value', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: String, - }, - }) - - db.user.create({ - id: 'abc-123', - }) - - expect( - db.user.update({ - where: { - id: { - equals: 'abc-123', - }, - }, - data: { - firstName: 'John', - }, - }), - ).toEqual({ - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'abc-123', - firstName: 'John', - }) - - expect( - db.user.findFirst({ - where: { - id: { - equals: 'abc-123', - }, - }, - }), - ).toEqual({ - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'abc-123', - firstName: 'John', - }) -}) - -test('updates the first entity when multiple entities match the query', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: name.findName, - followersCount: datatype.number, - }, - }) - db.user.create({ - firstName: 'Alice', - followersCount: 10, - }) - db.user.create({ - followersCount: 12, - }) - - const updatedUser = db.user.update({ - where: { - followersCount: { - gte: 10, - }, - }, - data: { - firstName: 'Kate', - }, - }) - expect(updatedUser).toHaveProperty('firstName', 'Kate') - - const kate = db.user.findFirst({ - where: { - firstName: { - equals: 'Kate', - }, - }, - }) - expect(kate).toHaveProperty('firstName', 'Kate') -}) - -test('updates a nested property of the model', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - address: { - billing: { - street: String, - country: String, - }, - shipping: { - country: String, - }, - }, - }, - }) - - db.user.create({ - id: 'user-1', - address: { - billing: { - street: 'Baker', - country: 'us', - }, - shipping: { - country: 'de', - }, - }, - }) - - const updatedUser = db.user.update({ - where: { - id: { - equals: 'user-1', - }, - }, - data: { - address: { - billing: { - country: 'de', - }, - }, - }, - }) - - expect(updatedUser).toHaveProperty(['address', 'billing', 'street'], 'Baker') - expect(updatedUser).toHaveProperty(['address', 'billing', 'country'], 'de') - - const queriedUser = db.user.findFirst({ - where: { - address: { - billing: { - country: { - equals: 'de', - }, - }, - }, - }, - }) - expect(queriedUser).toEqual(updatedUser) -}) - -test('updates root and nested properties of the model simultaneously', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: String, - address: { - shipping: { - country: String, - }, - }, - }, - }) - - db.user.create({ - id: 'user-1', - firstName: 'Lora', - address: { - shipping: { - country: 'de', - }, - }, - }) - - const updatedUser = db.user.update({ - where: { - id: { - equals: 'user-1', - }, - }, - data: { - firstName: 'Bob', - address: { - shipping: { - country: 'fr', - }, - }, - }, - }) - - expect(updatedUser).toEqual({ - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'user-1', - firstName: 'Bob', - address: { - shipping: { - country: 'fr', - }, - }, - }) -}) - -test('updates both properties and relations', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: String, - address: oneOf('address'), - }, - address: { - id: primaryKey(datatype.uuid), - country: String, - }, - }) - - db.user.create({ - id: 'user-1', - firstName: 'Lora', - address: db.address.create({ - id: 'address-1', - country: 'de', - }), - }) - - const newAddress = db.address.create({ - id: 'address-2', - country: 'us', - }) - - const updatedUser = db.user.update({ - where: { - id: { - equals: 'user-1', - }, - }, - data: { - firstName: 'Bob', - address: newAddress, - }, - }) - - expect(updatedUser).toEqual({ - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'user-1', - firstName: 'Bob', - address: { - [ENTITY_TYPE]: 'address', - [PRIMARY_KEY]: 'id', - id: 'address-2', - country: 'us', - }, - }) -}) - -test('throws an exception when no model matches the query in strict mode', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: String, - }, - }) - db.user.create() - db.user.create() - - const error = getThrownError(() => { - db.user.update({ - where: { - id: { - equals: 'abc-123', - }, - }, - data: { - firstName: 'John', - }, - strict: true, - }) - }) - - expect(error).toHaveProperty('name', 'OperationError') - expect(error).toHaveProperty('type', OperationErrorType.EntityNotFound) - expect(error).toHaveProperty( - 'message', - 'Failed to execute "update" on the "user" model: no entity found matching the query "{"id":{"equals":"abc-123"}}".', - ) -}) - -test('moves the entity when it updates the primary key', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - }, - }) - - db.user.create({ - id: 'abc-123', - }) - - const updatedUser = db.user.update({ - where: { - id: { - equals: 'abc-123', - }, - }, - data: { - id: 'def-456', - }, - }) - expect(updatedUser).toHaveProperty('id', 'def-456') - - const userResult = db.user.findFirst({ - where: { - id: { - equals: 'def-456', - }, - }, - }) - expect(userResult).toHaveProperty('id', 'def-456') - - const oldUser = db.user.findFirst({ - where: { - id: { - equals: 'abc-123', - }, - }, - }) - expect(oldUser).toBeNull() -}) - -test('does nothing when no entity matches the query', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - }, - }) - - db.user.create() - db.user.create() - - const updatedUser = db.user.update({ - where: { - id: { - equals: 'abc-123', - }, - }, - data: { - id: 'def-456', - }, - }) - expect(updatedUser).toBeNull() -}) - -test('throw an error when trying to update an entity using a key already used', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - }, - }) - - db.user.create({ - id: '123', - }) - db.user.create({ - id: '456', - }) - - const error = getThrownError(() => { - db.user.update({ - where: { - id: { - equals: '456', - }, - }, - data: { - id: '123', - }, - }) - }) - - expect(error).toHaveProperty('name', 'OperationError') - expect(error).toHaveProperty('type', OperationErrorType.DuplicatePrimaryKey) - expect(error).toHaveProperty( - 'message', - 'Failed to execute "update" on the "user" model: the entity with a primary key "123" ("id") already exists.', - ) -}) - -test('derives next entity values based on the existing ones', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: name.findName, - role: String, - }, - }) - - db.user.create({ - firstName: 'John', - role: 'Auditor', - }) - db.user.create({ - firstName: 'Jessie', - role: 'Writer', - }) - - db.user.update({ - where: { - role: { - equals: 'Auditor', - }, - }, - data: { - firstName(firstName) { - return firstName.toUpperCase() - }, - role(role, user) { - return user.firstName === 'John' ? 'Writer' : role - }, - }, - }) - - const userResult = db.user.findFirst({ - where: { - firstName: { - equals: 'JOHN', - }, - }, - }) - expect(userResult).toHaveProperty('firstName', 'JOHN') - expect(userResult).toHaveProperty('role', 'Writer') -}) - -test('exposes a root entity for a derivitive value of a nested property', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - address: { - billing: { - country: String, - }, - }, - }, - }) - - db.user.create({ - id: 'abc-123', - address: { - billing: { - country: 'us', - }, - }, - }) - - const result = db.user.update({ - where: { - id: { - equals: 'abc-123', - }, - }, - data: { - address: { - billing: { - country(country, user) { - expect(user).toHaveProperty('id', 'abc-123') - expect(user).toHaveProperty(['address', 'billing', 'country'], 'us') - - return country.toUpperCase() - }, - }, - }, - }, - }) - - expect(result).toHaveProperty(['address', 'billing', 'country'], 'US') -}) - -test('supports updating a nullable property to a non-null value', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: nullable(name.firstName), - }, - }) - - db.user.create({ - id: 'abc-123', - firstName: null, - }) - - expect( - db.user.update({ - where: { - id: { - equals: 'abc-123', - }, - }, - data: { - firstName: 'John', - }, - }), - ).toHaveProperty('firstName', 'John') -}) - -test('supports updating a nullable property with a value to null', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: nullable(name.firstName), - }, - }) - - db.user.create({ - id: 'abc-123', - firstName: 'John', - }) - - expect( - db.user.update({ - where: { - id: { - equals: 'abc-123', - }, - }, - data: { - firstName: null, - }, - }), - ).toHaveProperty('firstName', null) -}) - -test('throws when setting a non-nullable property to null', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: name.firstName, - }, - }) - - db.user.create({ - id: 'abc-123', - }) - - expect(() => - db.user.update({ - where: { - id: { - equals: 'abc-123', - }, - }, - data: { - // @ts-expect-error types don't allow updating normal properties to null - firstName: null, - }, - }), - ).toThrow( - 'Failed to update "firstName" on "user": cannot set a non-nullable property to null.', - ) -}) diff --git a/test/model/update/collocated-update.test.ts b/test/model/update/collocated-update.test.ts deleted file mode 100644 index 1bc8e16d..00000000 --- a/test/model/update/collocated-update.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { factory, primaryKey, oneOf } from '../../../src' - -it.skip('supports a collocated update of a parent its ONE_OF relationship', () => { - const db = factory({ - post: { - id: primaryKey(String), - title: String, - revision: oneOf('revision'), - }, - revision: { - id: primaryKey(String), - isReviewed: Boolean, - }, - }) - - db.post.create({ - id: 'post-1', - title: 'Initial title', - revision: db.revision.create({ - id: 'revision-1', - isReviewed: false, - }), - }) - - const nextPost = db.post.update({ - where: { id: { equals: 'post-1' } }, - // @ts-ignore - data: { - title: 'Next title', - revision(revision) { - // Update the "post.revision" from within the "post" update. - return db.revision.update({ - where: { id: { equals: revision.id } }, - data: { - isReviewed: true, - }, - })! - }, - }, - })! - - // Revision on the updated "post" returns the updated entity. - expect(nextPost.title).toEqual('Next title') - expect(nextPost.revision?.isReviewed).toEqual(true) - - // Revision on a newly queried post returns the updated entity. - const latestPost = db.post.findFirst({ where: { id: { equals: 'post-1' } } })! - expect(latestPost.title).toEqual('Next title') - expect(latestPost.revision?.isReviewed).toEqual(true) - - // Direct query on the revision (relational property) returns the updated entity. - const revision = db.revision.findFirst({ - where: { id: { equals: 'revision-1' } }, - })! - expect(revision.isReviewed).toEqual(true) -}) diff --git a/test/model/updateMany.test.ts b/test/model/updateMany.test.ts deleted file mode 100644 index b08906b2..00000000 --- a/test/model/updateMany.test.ts +++ /dev/null @@ -1,297 +0,0 @@ -import { datatype, name } from 'faker' -import { factory, primaryKey, nullable } from '../../src' -import { OperationErrorType } from '../../src/errors/OperationError' -import { getThrownError } from '../testUtils' - -test('derives updated value from the existing value', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: name.findName, - role: String, - }, - }) - db.user.create({ - firstName: 'Joseph', - role: 'Auditor', - }) - db.user.create({ - firstName: 'Jack', - role: 'Writer', - }) - db.user.create({ - firstName: 'John', - role: 'Auditor', - }) - - const updateMultiUsers = db.user.updateMany({ - where: { - role: { - equals: 'Auditor', - }, - }, - data: { - firstName(firstName) { - return firstName.toUpperCase() - }, - role(role, user) { - return user.firstName === 'John' ? 'Writer' : role - }, - }, - })! - - expect(updateMultiUsers).toHaveLength(2) - const names = updateMultiUsers.map((user) => user.firstName) - const roles = updateMultiUsers.map((user) => user.role) - expect(names).toEqual(['JOSEPH', 'JOHN']) - expect(roles).toEqual(['Auditor', 'Writer']) - - const userResult = db.user.findMany({ - where: { - role: { - equals: 'Auditor', - }, - }, - }) - const allFirstNames = userResult.map((user) => user.firstName) - // "John" is no longer in the results because it's role changed to "Writer". - expect(allFirstNames).toEqual(['JOSEPH']) -}) - -test('moves entities when they update primary keys', () => { - const db = factory({ - user: { - id: primaryKey(String), - }, - }) - db.user.create({ id: 'a' }) - db.user.create({ id: 'b' }) - db.user.create({ id: 'c' }) - - db.user.updateMany({ - where: { - id: { - in: ['a', 'b'], - }, - }, - data: { - id: (value) => value + 1, - }, - }) - - const updatedUsers = db.user.findMany({ - where: { - id: { - in: ['a1', 'b1'], - }, - }, - }) - expect(updatedUsers).toHaveLength(2) - const updatedUserIds = updatedUsers.map((user) => user.id) - expect(updatedUserIds).toEqual(['a1', 'b1']) - - const oldUsers = db.user.findMany({ - where: { - id: { - in: ['a', 'b'], - }, - }, - }) - expect(oldUsers).toHaveLength(0) - - const intactUser = db.user.findFirst({ - where: { - id: { equals: 'c' }, - }, - }) - expect(intactUser).toHaveProperty('id', 'c') -}) - -test('throws an exception when no entity matches the query in strict mode', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: name.firstName, - }, - }) - db.user.create() - db.user.create() - - const error = getThrownError(() => { - db.user.updateMany({ - where: { - id: { - in: ['abc-123', 'def-456'], - }, - }, - data: { - firstName: (value) => value.toUpperCase(), - }, - strict: true, - }) - }) - - expect(error).toHaveProperty('name', 'OperationError') - expect(error).toHaveProperty('type', OperationErrorType.EntityNotFound) - expect(error).toHaveProperty( - 'message', - 'Failed to execute "updateMany" on the "user" model: no entities found matching the query "{"id":{"in":["abc-123","def-456"]}}".', - ) -}) - -test('should update many entities with primitive values', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: name.findName, - role: String, - }, - }) - db.user.create({ - firstName: 'Joseph', - role: 'Auditor', - }) - - db.user.create({ - firstName: 'John', - role: 'Auditor', - }) - - db.user.create({ - firstName: 'Jack', - role: 'Writer', - }) - - const updateMultiUsers = db.user.updateMany({ - where: { - role: { - equals: 'Auditor', - }, - }, - data: { - role: 'Admin', - }, - })! - - expect(updateMultiUsers).toHaveLength(2) - updateMultiUsers.forEach((user) => expect(user.role).toEqual('Admin')) -}) - -test('supports updating a nullable property to a non-null value on many entities', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: name.findName, - role: nullable(() => null), - }, - }) - db.user.create({ - firstName: 'Joseph', - role: null, - }) - - db.user.create({ - firstName: 'John', - role: null, - }) - - db.user.create({ - firstName: 'Jack', - role: 'Writer', - }) - - const nextAdmins = db.user.updateMany({ - where: { - firstName: { - contains: 'J', - }, - }, - data: { - role: 'Admin', - }, - })! - - expect(nextAdmins).toHaveLength(3) - nextAdmins.forEach((user) => expect(user).toHaveProperty('role', 'Admin')) -}) - -test('supports updating a nullable property with a value to null on many entities', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: name.findName, - role: nullable(() => null), - }, - }) - db.user.create({ - firstName: 'Joseph', - role: 'Auditor', - }) - - db.user.create({ - firstName: 'John', - role: 'Auditor', - }) - - db.user.create({ - firstName: 'Jack', - role: 'Writer', - }) - - const prevAuditors = db.user.updateMany({ - where: { - role: { - equals: 'Auditor', - }, - }, - data: { - role: null, - }, - })! - - expect(prevAuditors).toHaveLength(2) - prevAuditors.forEach((user) => expect(user.role).toBeNull()) -}) - -test('throw an error when updating entities with an already existing primary key', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - role: String, - }, - }) - - db.user.create({ - id: '123', - role: 'Admin', - }) - db.user.create({ - id: '456', - role: 'Auditor', - }) - - db.user.create({ - id: '789', - role: 'Auditor', - }) - - const error = getThrownError(() => { - db.user.updateMany({ - where: { - role: { - equals: 'Auditor', - }, - }, - data: { - id: '123', - }, - }) - }) - - expect(error).toHaveProperty('name', 'OperationError') - expect(error).toHaveProperty('type', OperationErrorType.DuplicatePrimaryKey) - expect(error).toHaveProperty( - 'message', - 'Failed to execute "updateMany" on the "user" model: the entity with a primary key "123" ("id") already exists.', - ) -}) diff --git a/test/performance/performance.test.ts b/test/performance/performance.test.ts deleted file mode 100644 index 75ff0b3a..00000000 --- a/test/performance/performance.test.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { datatype, random, name } from 'faker' -import { factory, primaryKey } from '@mswjs/data' -import { measurePerformance, repeat } from '../testUtils' - -describe.skip('Performance testing', () => { - test('creates a 1000 records in under 100ms', async () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: name.firstName, - lastName: name.lastName, - age: datatype.number, - role: random.word, - }, - }) - - const createPerformance = await measurePerformance('create', () => { - repeat(db.user.create, 1000) - }) - - expect(createPerformance.duration).toBeLessThanOrEqual(350) - }) - - test('queries through a 1000 records in under 100ms', async () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: name.firstName, - lastName: name.lastName, - age: datatype.number, - role: random.word, - }, - }) - repeat(db.user.create, 1000) - - const findManyPerformance = await measurePerformance('findMany', () => { - db.user.findMany({ - where: { - age: { - gte: 18, - }, - }, - }) - }) - - expect(findManyPerformance.duration).toBeLessThanOrEqual(350) - }) - - test('updates a single record under 100ms', async () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: name.firstName, - lastName: name.lastName, - age: datatype.number, - role: random.word, - }, - }) - repeat(db.user.create, 1000) - - const updatePerformance = await measurePerformance('update', () => { - db.user.update({ - where: { - age: { - lte: 20, - }, - }, - data: { - age: 21, - }, - }) - }) - - expect(updatePerformance.duration).toBeLessThanOrEqual(350) - }) - - test('deletes a single record in under 100ms', async () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: name.firstName, - lastName: name.lastName, - age: datatype.number, - role: random.word, - }, - }) - repeat(db.user.create, 999) - db.user.create({ id: 'abc-123' }) - - const deletePerformance = await measurePerformance('delete', () => { - db.user.delete({ - where: { - id: { - equals: 'abc-123', - }, - }, - }) - }) - - expect(deletePerformance.duration).toBeLessThanOrEqual(350) - }) - - test('deletes multiple records in under 100ms', async () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: name.firstName, - lastName: name.lastName, - age: datatype.number, - role: random.word, - }, - }) - repeat(db.user.create, 1000) - - const deleteManyPerformance = await measurePerformance('deleteMany', () => { - db.user.deleteMany({ - where: { - age: { - lte: 18, - }, - }, - }) - }) - - expect(deleteManyPerformance.duration).toBeLessThanOrEqual(350) - }) -}) diff --git a/test/primaryKey.test.ts b/test/primaryKey.test.ts deleted file mode 100644 index f48574f8..00000000 --- a/test/primaryKey.test.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { v4 } from 'uuid' -import { random, datatype } from 'faker' -import { factory, primaryKey } from '../src' -import { - OperationError, - OperationErrorType, -} from '../src/errors/OperationError' -import { getThrownError } from './testUtils' - -test('supports querying by the primary key', () => { - const db = factory({ - user: { - id: primaryKey(v4), - firstName: random.word, - }, - }) - - db.user.create() - db.user.create() - const user = db.user.create({ - firstName: 'John', - }) - db.user.create() - - const userResult = db.user.findFirst({ - where: { - id: { - equals: user.id, - }, - }, - }) - - expect(userResult).toHaveProperty('id', user.id) - expect(userResult).toHaveProperty('firstName', 'John') -}) - -test('supports querying by the range of primary keys', () => { - const db = factory({ - user: { - id: primaryKey(random.word), - firstName: random.word, - }, - }) - - db.user.create({ - id: 'abc-123', - firstName: 'John', - }) - db.user.create() - db.user.create({ - id: 'def-456', - firstName: 'Kate', - }) - db.user.create() - - const results = db.user.findMany({ - where: { - id: { - in: ['abc-123', 'def-456'], - }, - }, - }) - expect(results).toHaveLength(2) - - const userNames = results.map((user) => user.firstName) - expect(userNames).toEqual(['John', 'Kate']) -}) - -test('supports querying by the primary key and additional properties', () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: String, - age: Number, - }, - }) - - db.user.create({ - id: 'abc-123', - firstName: 'John', - age: 32, - }) - db.user.create({ - firstName: 'Alice', - age: 23, - }) - db.user.create({ - id: 'def-456', - firstName: 'Kate', - age: 14, - }) - db.user.create({ - firstName: 'Sheldon', - age: 42, - }) - - const results = db.user.findMany({ - where: { - id: { - in: ['abc-123', 'def-456'], - }, - age: { - gte: 18, - }, - }, - }) - expect(results).toHaveLength(1) - - expect(results[0]).toHaveProperty('firstName', 'John') - expect(results[0]).toHaveProperty('age', 32) -}) - -test('throws an exception when creating entity with existing primary key', () => { - const db = factory({ - user: { - id: primaryKey(v4), - }, - }) - - db.user.create({ id: 'abc-123' }) - - expect(() => { - db.user.create({ id: 'abc-123' }) - }).toThrowError( - new OperationError( - OperationErrorType.DuplicatePrimaryKey, - 'Failed to create a "user" entity: an entity with the same primary key "abc-123" ("id") already exists.', - ), - ) -}) - -test('throws an error when primary key is not set at root level', () => { - const error = getThrownError(() => { - factory({ - user: { - name: String, - info: { - // @ts-expect-error Primary key on nested properties are forbidden. - id: primaryKey(datatype.uuid), - firstName: String, - lastName: String, - }, - }, - }) - }) - expect(error).toHaveProperty( - 'message', - 'Failed to parse a model definition for "info" property of "user": cannot have a primary key in a nested object.', - ) -}) diff --git a/test/query/boolean.test.ts b/test/query/boolean.test.ts deleted file mode 100644 index ed664d13..00000000 --- a/test/query/boolean.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { datatype } from 'faker' -import { factory, primaryKey, nullable } from '../../src' - -const setup = () => { - const db = factory({ - book: { - id: primaryKey(datatype.uuid), - title: String, - published: Boolean, - finished: nullable(() => null), - }, - }) - - db.book.create({ - title: 'The Winds of Winter', - published: false, - finished: false, - }) - db.book.create({ - title: 'New Spring', - published: true, - finished: true, - }) - db.book.create({ - title: 'The Doors of Stone', - published: false, - finished: null, // Who knows with Patrick? - }) - db.book.create({ - title: 'The Fellowship of the Ring', - published: true, - finished: true, - }) - - return db -} - -test('queries entities based on a boolean value', () => { - const db = setup() - - const firstPublished = db.book.findFirst({ - where: { - published: { - equals: true, - }, - }, - }) - expect(firstPublished).toHaveProperty('title', 'New Spring') - - const allUnpublished = db.book.findMany({ - where: { - published: { - notEquals: true, - }, - }, - }) - expect(allUnpublished).toHaveLength(2) - - const unpublishedTitles = allUnpublished.map((book) => book.title) - expect(unpublishedTitles).toEqual([ - 'The Winds of Winter', - 'The Doors of Stone', - ]) -}) - -test('ignores entities with missing values when querying using boolean', () => { - const db = setup() - - const finishedBooks = db.book.findMany({ - where: { finished: { equals: true } }, - }) - const unfinishedBooks = db.book.findMany({ - where: { finished: { notEquals: true } }, - }) - const bookTitles = [...finishedBooks, ...unfinishedBooks].map( - (book) => book.title, - ) - - expect(bookTitles).toHaveLength(3) - expect(bookTitles).not.toContain('The Doors of Stone') -}) diff --git a/test/query/date.test.ts b/test/query/date.test.ts deleted file mode 100644 index 26b84502..00000000 --- a/test/query/date.test.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { datatype } from 'faker' -import { factory, primaryKey, nullable } from '../../src' - -const setup = () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: String, - createdAt: () => new Date(), - updatedAt: nullable(() => null), - }, - }) - db.user.create({ - firstName: 'John', - createdAt: new Date('1980-04-12'), - updatedAt: new Date('1980-04-12'), - }) - db.user.create({ - firstName: 'Kate', - createdAt: new Date('2013-08-09'), - updatedAt: new Date('2014-01-01'), - }) - db.user.create({ - firstName: 'Sedrik', - createdAt: new Date('1980-04-12'), - }) - return db -} - -test('queries entities that equal a date', () => { - const db = setup() - - const userResults = db.user.findMany({ - where: { - createdAt: { - equals: new Date('1980-04-12'), - }, - }, - }) - expect(userResults).toHaveLength(2) - - const userNames = userResults.map((user) => user.firstName) - expect(userNames).toEqual(['John', 'Sedrik']) -}) - -test('queries entities that do not equal a date', () => { - const db = setup() - - const userResults = db.user.findMany({ - where: { - createdAt: { - notEquals: new Date('1980-04-12'), - }, - }, - }) - expect(userResults).toHaveLength(1) - - const userNames = userResults.map((user) => user.firstName) - expect(userNames).toEqual(['Kate']) -}) - -test('queries entities that are older than a date', () => { - const db = setup() - - const userResults = db.user.findMany({ - where: { - createdAt: { - lt: new Date('1980-04-14'), - }, - }, - }) - expect(userResults).toHaveLength(2) - - const userNames = userResults.map((user) => user.firstName) - expect(userNames).toEqual(['John', 'Sedrik']) -}) - -test('queries entities that are older or equal a date', () => { - const db = setup() - - const userResults = db.user.findMany({ - where: { - createdAt: { - lte: new Date('1980-04-14'), - }, - }, - }) - expect(userResults).toHaveLength(2) - - const userNames = userResults.map((user) => user.firstName) - expect(userNames).toEqual(['John', 'Sedrik']) -}) - -test('queries entities that are newer than a date', () => { - const db = setup() - - const userResults = db.user.findMany({ - where: { - createdAt: { - gt: new Date('1980-04-14'), - }, - }, - }) - expect(userResults).toHaveLength(1) - - const userNames = userResults.map((user) => user.firstName) - expect(userNames).toEqual(['Kate']) -}) - -test('queries entities that are newer or equal to a date', () => { - const db = setup() - - const userResults = db.user.findMany({ - where: { - createdAt: { - gte: new Date('1980-04-14'), - }, - }, - }) - expect(userResults).toHaveLength(1) - - const userNames = userResults.map((user) => user.firstName) - expect(userNames).toEqual(['Kate']) -}) - -test('ignores entities with missing values when querying using date', () => { - const db = setup() - - const date = new Date('2000-01-01') - const updatedBefore = db.user.findMany({ - where: { updatedAt: { lte: date } }, - }) - const updatedAfter = db.user.findMany({ - where: { updatedAt: { gte: date } }, - }) - const updatedUserNames = [...updatedBefore, ...updatedAfter].map( - (user) => user.firstName, - ) - - expect(updatedUserNames).toHaveLength(2) - expect(updatedUserNames).not.toContain('Sedrick') -}) diff --git a/test/query/number.test.ts b/test/query/number.test.ts deleted file mode 100644 index cde686bd..00000000 --- a/test/query/number.test.ts +++ /dev/null @@ -1,209 +0,0 @@ -import { datatype } from 'faker' -import { factory, primaryKey, nullable } from '../../src' - -const setup = () => { - const db = factory({ - user: { - id: primaryKey(datatype.uuid), - firstName: String, - age: Number, - height: nullable(() => null), - }, - }) - db.user.create({ - firstName: 'John', - age: 16, - height: 200, - }) - db.user.create({ - firstName: 'Alice', - age: 24, - height: 165, - }) - db.user.create({ - firstName: 'Kate', - age: 41, - }) - - return db -} - -test('queries entities where property equals to a number', () => { - const db = setup() - - const firstAdult = db.user.findFirst({ - where: { - age: { - gte: 18, - }, - }, - }) - expect(firstAdult).toHaveProperty('firstName', 'Alice') - - const allAdults = db.user.findMany({ - where: { - age: { - gte: 18, - }, - }, - }) - expect(allAdults).toHaveLength(2) - const adultsNames = allAdults.map((user) => user.firstName) - expect(adultsNames).toEqual(['Alice', 'Kate']) -}) - -test('queries entities where property is not equals to a number', () => { - const db = setup() - - const users = db.user.findMany({ - where: { - age: { - notEquals: 24, - }, - }, - }) - expect(users).toHaveLength(2) - const names = users.map((user) => user.firstName) - expect(names).toEqual(['John', 'Kate']) -}) - -test('queries entities where property is within a number range', () => { - const db = setup() - - const john = db.user.findFirst({ - where: { - age: { - between: [16, 34], - }, - }, - }) - expect(john).toHaveProperty('firstName', 'John') - - const usersInAge = db.user.findMany({ - where: { - age: { - between: [16, 34], - }, - }, - }) - expect(usersInAge).toHaveLength(2) - const names = usersInAge.map((user) => user.firstName) - expect(names).toEqual(['John', 'Alice']) -}) - -test('queries entities where property is not within a number range', () => { - const db = setup() - - const users = db.user.findMany({ - where: { - age: { - notBetween: [16, 34], - }, - }, - }) - expect(users).toHaveLength(1) - const names = users.map((user) => user.firstName) - expect(names).toEqual(['Kate']) -}) - -test('queries entities that are older than a number', () => { - const db = setup() - - const users = db.user.findMany({ - where: { - age: { - gt: 23, - }, - }, - }) - expect(users).toHaveLength(2) - const names = users.map((user) => user.firstName) - expect(names).toEqual(['Alice', 'Kate']) -}) - -test('queries entities that are older or equal a number', () => { - const db = setup() - - const users = db.user.findMany({ - where: { - age: { - gte: 24, - }, - }, - }) - expect(users).toHaveLength(2) - const names = users.map((user) => user.firstName) - expect(names).toEqual(['Alice', 'Kate']) -}) - -test('queries entities that are younger then a number', () => { - const db = setup() - - const users = db.user.findMany({ - where: { - age: { - lt: 24, - }, - }, - }) - expect(users).toHaveLength(1) - const names = users.map((user) => user.firstName) - expect(names).toEqual(['John']) -}) - -test('queries entities that are younger or equal a number', () => { - const db = setup() - - const users = db.user.findMany({ - where: { - age: { - lte: 24, - }, - }, - }) - expect(users).toHaveLength(2) - const names = users.map((user) => user.firstName) - expect(names).toEqual(['John', 'Alice']) -}) - -test('queries entities where property is not contained into the array', () => { - const db = setup() - - const users = db.user.findMany({ - where: { - age: { - notIn: [16, 24], - }, - }, - }) - const names = users.map((user) => user.firstName) - expect(names).toEqual(['Kate']) -}) - -test('queries entities where property is contained into the array', () => { - const db = setup() - - const users = db.user.findMany({ - where: { - age: { - in: [16, 24], - }, - }, - }) - const names = users.map((user) => user.firstName) - expect(names).toEqual(['John', 'Alice']) -}) - -test('ignores entities with missing values when querying using number', () => { - const db = setup() - - const height = 180 - const shorterUsers = db.user.findMany({ where: { height: { lt: height } } }) - const tallerUsers = db.user.findMany({ where: { height: { gte: height } } }) - const userNames = [...shorterUsers, ...tallerUsers].map( - (user) => user.firstName, - ) - - expect(userNames).toHaveLength(2) - expect(userNames).toEqual(['Alice', 'John']) -}) diff --git a/test/query/pagination.test.ts b/test/query/pagination.test.ts deleted file mode 100644 index 1996ee70..00000000 --- a/test/query/pagination.test.ts +++ /dev/null @@ -1,435 +0,0 @@ -import { datatype } from 'faker' -import { factory, primaryKey, oneOf } from '../../src' - -test('supports offset-based pagination', () => { - const db = factory({ - book: { - id: primaryKey(datatype.uuid), - title: String, - category: String, - }, - }) - - db.book.create({ title: 'Magician', category: 'Fantasy' }) - db.book.create({ title: 'Irrelevant Book #1', category: 'Science' }) - db.book.create({ title: 'The Lord of the Rings', category: 'Fantasy' }) - db.book.create({ title: 'The Name of the Wind', category: 'Fantasy' }) - db.book.create({ title: 'Irrelevant Book #2', category: 'Science' }) - db.book.create({ title: 'The Song of Ice and Fire', category: 'Fantasy' }) - db.book.create({ title: 'Irrelevant Book #3', category: 'Science' }) - - const firstPage = db.book.findMany({ - where: { category: { equals: 'Fantasy' } }, - take: 2, - }) - expect(firstPage).toHaveLength(2) - const firstPageBooks = firstPage.map((book) => book.title) - expect(firstPageBooks).toEqual(['Magician', 'The Lord of the Rings']) - - const secondPage = db.book.findMany({ - where: { category: { equals: 'Fantasy' } }, - skip: 2, - take: 2, - }) - expect(secondPage).toHaveLength(2) - const secondPageBooks = secondPage.map((book) => book.title) - expect(secondPageBooks).toEqual([ - 'The Name of the Wind', - 'The Song of Ice and Fire', - ]) -}) - -test('supports cursor-based pagination', () => { - const db = factory({ - book: { - id: primaryKey(datatype.uuid), - title: String, - category: String, - }, - }) - - db.book.create({ title: 'Magician', category: 'Fantasy' }) - db.book.create({ title: 'Irrelevant Book #1', category: 'Science' }) - db.book.create({ title: 'The Lord of the Rings', category: 'Fantasy' }) - db.book.create({ title: 'The Name of the Wind', category: 'Fantasy' }) - db.book.create({ title: 'Irrelevant Book #2', category: 'Science' }) - db.book.create({ title: 'The Song of Ice and Fire', category: 'Fantasy' }) - db.book.create({ title: 'Irrelevant Book #3', category: 'Science' }) - - const firstPage = db.book.findMany({ - where: { category: { equals: 'Fantasy' } }, - take: 2, - cursor: null, - }) - expect(firstPage).toHaveLength(2) - const firstPageBooks = firstPage.map((book) => book.title) - expect(firstPageBooks).toEqual(['Magician', 'The Lord of the Rings']) - - const secondPage = db.book.findMany({ - where: { category: { equals: 'Fantasy' } }, - take: 2, - cursor: firstPage[firstPage.length - 1].id, - }) - expect(secondPage).toHaveLength(2) - const secondPageBooks = secondPage.map((book) => book.title) - expect(secondPageBooks).toEqual([ - 'The Name of the Wind', - 'The Song of Ice and Fire', - ]) -}) - -test('returns an empty list given invalid cursor', () => { - const db = factory({ - book: { - id: primaryKey(datatype.uuid), - title: String, - category: String, - }, - }) - - db.book.create({ title: 'Magician', category: 'Fantasy' }) - db.book.create({ title: 'Irrelevant Book #1', category: 'Science' }) - db.book.create({ title: 'The Lord of the Rings', category: 'Fantasy' }) - db.book.create({ title: 'Irrelevant Book #2', category: 'Science' }) - - const firstPage = db.book.findMany({ - where: { category: { equals: 'Fantasy' } }, - take: 2, - cursor: 'abc-invalid-cursor', - }) - expect(firstPage).toEqual([]) -}) - -test('supports single-criteria sorting in the paginated results', () => { - const db = factory({ - book: { - id: primaryKey(datatype.uuid), - title: String, - publishedYear: Number, - }, - }) - - db.book.create({ title: 'B', publishedYear: 1875 }) - db.book.create({ title: 'C', publishedYear: 2004 }) - db.book.create({ title: 'A', publishedYear: 2021 }) - db.book.create({ title: 'D', publishedYear: 2006 }) - - const firstPage = db.book.findMany({ - where: { - publishedYear: { gte: 1990 }, - }, - take: 2, - orderBy: { title: 'asc' }, - }) - const firstPageTitles = firstPage.map((book) => book.title) - expect(firstPageTitles).toEqual(['A', 'C']) - - const secondPage = db.book.findMany({ - where: { - publishedYear: { gte: 1990 }, - }, - skip: 2, - take: 2, - orderBy: { - title: 'asc', - }, - }) - const secondPageTitles = secondPage.map((book) => book.title) - expect(secondPageTitles).toEqual(['D']) -}) - -test('supports multi-criteria sorting in the paginated results', () => { - const db = factory({ - book: { - id: primaryKey(datatype.uuid), - title: String, - publishedYear: Number, - }, - }) - - db.book.create({ title: 'A', publishedYear: 1875 }) - db.book.create({ title: 'C', publishedYear: 2004 }) - db.book.create({ title: 'A', publishedYear: 2021 }) - db.book.create({ title: 'D', publishedYear: 2006 }) - - const firstPage = db.book.findMany({ - take: 2, - orderBy: [ - { - title: 'asc', - }, - { - publishedYear: 'desc', - }, - ], - }) - const firstPageBooks = firstPage.map((book) => [ - book.title, - book.publishedYear, - ]) - expect(firstPageBooks).toEqual([ - ['A', 2021], - ['A', 1875], - ]) - - const secondPage = db.book.findMany({ - skip: 2, - take: 2, - orderBy: [ - { - title: 'asc', - }, - { - publishedYear: 'desc', - }, - ], - }) - const secondPageBooks = secondPage.map((book) => book.title) - expect(secondPageBooks).toEqual(['C', 'D']) -}) - -test('supports single-criteria sorting by relational property in the paginated results', () => { - const db = factory({ - book: { - id: primaryKey(datatype.uuid), - title: String, - author: oneOf('author'), - }, - author: { - id: primaryKey(datatype.uuid), - firstName: String, - }, - }) - - const john = db.author.create({ firstName: 'John' }) - const george = db.author.create({ firstName: 'George' }) - const nelson = db.author.create({ firstName: 'Nelson' }) - const bookByJohn = db.book.create({ title: 'A', author: john }) - const bookByGeorge = db.book.create({ title: 'B', author: george }) - db.book.create({ title: 'C', author: nelson }) - - const firstPage = db.book.findMany({ - take: 2, - orderBy: { - author: { - firstName: 'asc', - }, - }, - }) - - expect(firstPage).toEqual([bookByGeorge, bookByJohn]) -}) - -test('supports multi-criteria sorting by relational property in the paginated results', () => { - const db = factory({ - book: { - id: primaryKey(datatype.uuid), - title: String, - author: oneOf('author'), - }, - author: { - id: primaryKey(datatype.uuid), - firstName: String, - bornAt: () => new Date(), - }, - }) - - const john = db.author.create({ - firstName: 'John', - bornAt: new Date('1980-01-30'), - }) - const george = db.author.create({ - firstName: 'George', - bornAt: new Date('1990-12-08'), - }) - const nelson = db.author.create({ - firstName: 'Nelson', - bornAt: new Date('1986-09-09'), - }) - - const bookByJohn = db.book.create({ title: 'A', author: john }) - const bookByGeorge = db.book.create({ title: 'B', author: george }) - const bookByNelson = db.book.create({ title: 'C', author: nelson }) - - const firstPage = db.book.findMany({ - take: 2, - orderBy: [ - { - author: { - firstName: 'asc', - }, - }, - { - author: { - bornAt: 'desc', - }, - }, - ], - }) - - expect(firstPage).toEqual([bookByGeorge, bookByJohn]) - - const secondPage = db.book.findMany({ - skip: 2, - take: 2, - orderBy: [ - { - author: { - firstName: 'asc', - }, - }, - { - author: { - bornAt: 'desc', - }, - }, - ], - }) - - expect(secondPage).toEqual([bookByNelson]) -}) - -test('supports sorting by both direct and relational properties in the paginated results', () => { - const db = factory({ - book: { - id: primaryKey(datatype.uuid), - title: String, - author: oneOf('author'), - }, - author: { - id: primaryKey(datatype.uuid), - firstName: String, - }, - }) - - const john = db.author.create({ firstName: 'John' }) - const george = db.author.create({ firstName: 'George' }) - const nelson = db.author.create({ firstName: 'Nelson' }) - db.book.create({ title: 'A', author: john }) - db.book.create({ title: 'B', author: george }) - db.book.create({ title: 'A', author: nelson }) - - const firstPage = db.book.findMany({ - take: 2, - orderBy: [ - { - title: 'asc', - }, - { - author: { - firstName: 'asc', - }, - }, - ], - }) - const firstPageBooks = firstPage.map((book) => book.title) - const firstPageAuthors = firstPage.map((book) => book.author?.firstName) - expect(firstPageBooks).toEqual(['A', 'A']) - expect(firstPageAuthors).toEqual(['John', 'Nelson']) -}) - -test('supports single-criteria sorting by nested model properties', () => { - const db = factory({ - book: { - id: primaryKey(datatype.uuid), - publication: { - country: String, - }, - }, - }) - - const americanBook = db.book.create({ - publication: { - country: 'us', - }, - }) - - const germanBook = db.book.create({ - publication: { - country: 'de', - }, - }) - - const result = db.book.findMany({ - orderBy: { - publication: { - country: 'asc', - }, - }, - }) - - expect(result).toEqual([germanBook, americanBook]) -}) - -test('supports multi-criteria sorting by nested model properties', () => { - const db = factory({ - book: { - id: primaryKey(datatype.uuid), - publication: { - year: () => new Date(), - pubilsher: { - country: String, - }, - }, - }, - }) - - const americanBookOne = db.book.create({ - publication: { - year: new Date('1997-10-10'), - pubilsher: { - country: 'us', - }, - }, - }) - const americanBookTwo = db.book.create({ - publication: { - year: new Date('2005-04-01'), - pubilsher: { - country: 'us', - }, - }, - }) - - const germanBookOne = db.book.create({ - publication: { - year: new Date('2011-12-07'), - pubilsher: { - country: 'de', - }, - }, - }) - const germanBookTwo = db.book.create({ - publication: { - year: new Date('2020-06-24'), - pubilsher: { - country: 'de', - }, - }, - }) - - const result = db.book.findMany({ - where: {}, - orderBy: [ - { - publication: { - year: 'desc', - }, - }, - { - publication: { - pubilsher: { - country: 'asc', - }, - }, - }, - ], - }) - - expect(result).toEqual([ - germanBookTwo, - germanBookOne, - americanBookTwo, - americanBookOne, - ]) -}) diff --git a/test/query/string.test.ts b/test/query/string.test.ts deleted file mode 100644 index 55010a92..00000000 --- a/test/query/string.test.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { datatype } from 'faker' -import { factory, primaryKey, nullable } from '@mswjs/data' - -const setup = () => { - const db = factory({ - recipe: { - id: primaryKey(datatype.uuid), - title: String, - category: nullable(() => null), - }, - }) - db.recipe.create({ - title: 'New York Pizza', - category: 'pizza', - }) - db.recipe.create({ - title: 'Chocolate Cake', - category: 'cake', - }) - db.recipe.create({ - title: 'Pizza Mozzarrela', - category: 'pizza', - }) - db.recipe.create({ - title: 'Pizza Cake', - }) - return db -} - -test('queries entity where property equals a string', () => { - const db = setup() - - const firstPizza = db.recipe.findFirst({ - where: { - category: { - equals: 'pizza', - }, - }, - }) - expect(firstPizza).toHaveProperty('title', 'New York Pizza') - - const allPizza = db.recipe.findMany({ - where: { - category: { - equals: 'pizza', - }, - }, - }) - expect(allPizza).toHaveLength(2) - const titles = allPizza.map((pizza) => pizza.title) - expect(titles).toEqual(['New York Pizza', 'Pizza Mozzarrela']) -}) - -test('queries entities where property contains a string', () => { - const db = setup() - - const firstPizza = db.recipe.findFirst({ - where: { - title: { - contains: 'Pizza', - }, - }, - }) - expect(firstPizza).toHaveProperty('title', 'New York Pizza') - - const allPizzas = db.recipe.findMany({ - where: { - title: { - contains: 'Pizza', - }, - }, - }) - expect(allPizzas).toHaveLength(3) - const pizzaTitles = allPizzas.map((pizza) => pizza.title) - expect(pizzaTitles).toEqual([ - 'New York Pizza', - 'Pizza Mozzarrela', - 'Pizza Cake', - ]) -}) - -test('queries entities where property not contains a string', () => { - const db = setup() - - const chocolateCake = db.recipe.findFirst({ - where: { - title: { - notContains: 'Pizza', - }, - }, - }) - expect(chocolateCake).toHaveProperty('title', 'Chocolate Cake') -}) - -test('queries entities where property is not equals to a string', () => { - const db = setup() - - const chocolateCake = db.recipe.findFirst({ - where: { - title: { - notEquals: 'New York Pizza', - }, - }, - }) - expect(chocolateCake).toHaveProperty('title', 'Chocolate Cake') -}) - -test('queries entities where property is not contained into the array', () => { - const db = setup() - - const chocolateCake = db.recipe.findFirst({ - where: { - title: { - notIn: ['New York Pizza'], - }, - }, - }) - expect(chocolateCake).toHaveProperty('title', 'Chocolate Cake') -}) - -test('queries entities where property is contained into the array', () => { - const db = setup() - - const chocolateCake = db.recipe.findFirst({ - where: { - title: { - in: ['New York Pizza'], - }, - }, - }) - expect(chocolateCake).toHaveProperty('title', 'New York Pizza') -}) - -test('ignores entities with missing values when querying using strings', () => { - const db = setup() - - const pizzaOrCakeRecipes = db.recipe.findMany({ - where: { category: { in: ['pizza', 'cake'] } }, - }) - const pizzaOrCakeRecipeTitles = pizzaOrCakeRecipes.map( - (recipe) => recipe.title, - ) - - expect(pizzaOrCakeRecipeTitles).toHaveLength(3) - expect(pizzaOrCakeRecipeTitles).not.toContain('Pizza Cake') -}) diff --git a/test/regressions/02-handlers-many-of.test.ts b/test/regressions/02-handlers-many-of.test.ts deleted file mode 100644 index 7ac932f6..00000000 --- a/test/regressions/02-handlers-many-of.test.ts +++ /dev/null @@ -1,143 +0,0 @@ -import fetch from 'node-fetch' -import { rest } from 'msw' -import { setupServer } from 'msw/node' -import { factory, manyOf, primaryKey } from '../../src' -import { ENTITY_TYPE, PRIMARY_KEY } from '../../src/glossary' - -const server = setupServer() - -beforeAll(() => { - server.listen() -}) - -afterAll(() => { - server.close() -}) - -it('updates database entity modified via a generated request handler', async () => { - const db = factory({ - user: { - id: primaryKey(String), - notes: manyOf('note'), - }, - note: { - id: primaryKey(String), - title: String, - }, - }) - - db.user.create({ - id: 'user-1', - notes: [ - db.note.create({ id: 'note-1', title: 'First note' }), - db.note.create({ id: 'note-2', title: 'Second note' }), - ], - }) - - server.use( - rest.get('/user', (req, res, ctx) => { - const user = db.user.findFirst({ - strict: true, - where: { - id: { - equals: 'user-1', - }, - }, - }) - return res(ctx.json(user)) - }), - rest.put<{ title: string }>('/note/:noteId', (req, res, ctx) => { - const { noteId } = req.params - - const updatedNote = db.note.update({ - strict: true, - where: { - id: { - equals: noteId, - }, - }, - data: { - title: req.body.title, - }, - }) - - return res(ctx.json(updatedNote)) - }), - ) - - // Update a referenced relational property via request handler. - const noteUpdateResponse = await fetch('http://localhost/note/note-2', { - method: 'PUT', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - title: 'Updated title', - }), - }) - expect(noteUpdateResponse.status).toEqual(200) - - // Updates persist when querying the updated entity directly. - expect( - db.note.findFirst({ - where: { - id: { - equals: 'note-2', - }, - }, - }), - ).toEqual({ - [ENTITY_TYPE]: 'note', - [PRIMARY_KEY]: 'id', - id: 'note-2', - title: 'Updated title', - }) - - // Updates persist when querying a parent entity that references - // the updated relational entity. - expect( - db.user.findFirst({ - where: { - id: { - equals: 'user-1', - }, - }, - }), - ).toEqual({ - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'user-1', - notes: [ - { - [ENTITY_TYPE]: 'note', - [PRIMARY_KEY]: 'id', - id: 'note-1', - title: 'First note', - }, - { - [ENTITY_TYPE]: 'note', - [PRIMARY_KEY]: 'id', - id: 'note-2', - title: 'Updated title', - }, - ], - }) - - // Updates persist in the request handler's mocked response. - const refetchedUser = await fetch('http://localhost/user').then((res) => - res.json(), - ) - expect(refetchedUser).toEqual({ - id: 'user-1', - notes: [ - { - id: 'note-1', - title: 'First note', - }, - { - id: 'note-2', - title: 'Updated title', - }, - ], - }) -}) diff --git a/test/regressions/112-event-emitter-leak/112-event-emitter-leak.runtime.js b/test/regressions/112-event-emitter-leak/112-event-emitter-leak.runtime.js deleted file mode 100644 index c3286a3b..00000000 --- a/test/regressions/112-event-emitter-leak/112-event-emitter-leak.runtime.js +++ /dev/null @@ -1,13 +0,0 @@ -import { factory, primaryKey } from '@mswjs/data' - -const models = {} - -for (let i = 0; i < 100; i++) { - models[`model${i}`] = { - id: primaryKey(String), - } -} - -const db = factory(models) - -window.db = db diff --git a/test/regressions/112-event-emitter-leak/112-event-emitter-leak.test.ts b/test/regressions/112-event-emitter-leak/112-event-emitter-leak.test.ts deleted file mode 100644 index 1817160f..00000000 --- a/test/regressions/112-event-emitter-leak/112-event-emitter-leak.test.ts +++ /dev/null @@ -1,33 +0,0 @@ -/** - * @see https://github.com/mswjs/data/issues/112 - */ -import * as path from 'path' -import { CreateBrowserApi, createBrowser, pageWith } from 'page-with' - -let browser: CreateBrowserApi - -beforeAll(async () => { - browser = await createBrowser({ - serverOptions: { - webpackConfig: { - resolve: { - alias: { - '@mswjs/data': path.resolve(__dirname, '../../..'), - }, - }, - }, - }, - }) -}) - -afterAll(async () => { - await browser.cleanup() -}) - -it('creates numerous models in a browser without any memory leaks', async () => { - const runtime = await pageWith({ - example: path.resolve(__dirname, '112-event-emitter-leak.runtime.js'), - }) - - expect(runtime.consoleSpy.get('warning')).toBeUndefined() -}) diff --git a/test/relations/Relation.test.ts b/test/relations/Relation.test.ts deleted file mode 100644 index 1db93024..00000000 --- a/test/relations/Relation.test.ts +++ /dev/null @@ -1,354 +0,0 @@ -import { primaryKey } from '../../src' -import { ENTITY_TYPE, PRIMARY_KEY, ModelDictionary } from '../../src/glossary' -import { Database } from '../../src/db/Database' -import { - Relation, - RelationAttributes, - RelationKind, -} from '../../src/relations/Relation' - -it('supports creating a relation without attributes', () => { - const relation = new Relation({ - to: 'country', - kind: RelationKind.OneOf, - }) - - expect(relation).toBeInstanceOf(Relation) - expect(relation.kind).toEqual(RelationKind.OneOf) - expect(relation.target.modelName).toEqual('country') - expect(relation.attributes).toEqual({ - nullable: false, - unique: false, - }) -}) - -it('supports creating a relation with attributes', () => { - const relation = new Relation({ - to: 'country', - kind: RelationKind.ManyOf, - attributes: { - unique: true, - }, - }) - - expect(relation).toBeInstanceOf(Relation) - expect(relation.kind).toEqual(RelationKind.ManyOf) - expect(relation.target.modelName).toEqual('country') - expect(relation.attributes).toEqual({ - unique: true, - nullable: false, - }) -}) - -it('applies a "ONE_OF" relation to an entity', () => { - const relation = new Relation({ - to: 'country', - kind: RelationKind.OneOf, - }) - const dictionary: ModelDictionary = { - user: { - birthPlace: relation, - }, - country: { - code: primaryKey(String), - }, - } - const db = new Database(dictionary) - - db.create('country', { - [ENTITY_TYPE]: 'country', - [PRIMARY_KEY]: 'code', - code: 'us', - }) - db.getModel('country').get('us')! - - const users = db.create('user', { - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'user-1', - }) - const user = users.get('user-1')! - - relation.apply(user, ['birthPlace'], dictionary, db) - - // When applied, relation is updated with the additional info. - expect(relation.target.primaryKey).toEqual('code') - expect(relation.source.modelName).toEqual('user') - expect(relation.source.primaryKey).toEqual('id') - - // Applying a relation does NOT define the proxy getter. - expect(user).not.toHaveProperty('birthPlace') -}) - -it('applies a "MANY_OF" relation to an entity', () => { - const relation = new Relation({ - to: 'post', - kind: RelationKind.ManyOf, - }) - const dictionary: ModelDictionary = { - user: { - posts: relation, - }, - post: { - id: primaryKey(String), - }, - } - const db = new Database(dictionary) - - const users = db.create('user', { - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'user-1', - }) - const user = users.get('user-1')! - - db.create('post', { - [ENTITY_TYPE]: 'post', - [PRIMARY_KEY]: 'id', - id: 'post-1', - }) - db.create('post', { - [ENTITY_TYPE]: 'post', - [PRIMARY_KEY]: 'id', - id: 'post-2', - }) - db.getModel('post').get('post-1')! - db.getModel('post').get('post-2')! - - relation.apply(user, ['posts'], dictionary, db) - - expect(relation.source.modelName).toEqual('user') - expect(relation.source.primaryKey).toEqual('id') - expect(relation.target.primaryKey).toEqual('id') - - // Applying a relation does NOT define the proxy getter. - expect(user).not.toHaveProperty('posts') -}) - -it('throws an exception when resolving a relation with a non-existing reference', () => { - const relation = new Relation({ - to: 'country', - kind: RelationKind.OneOf, - }) - const dictionary: ModelDictionary = { - user: { - birthPlace: relation, - }, - country: { - code: primaryKey(String), - }, - } - const db = new Database(dictionary) - const users = db.create('user', { - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'user-1', - }) - const user = users.get('user-1')! - - relation.apply(user, ['birthPlace'], dictionary, db) - - expect(() => { - relation.resolveWith(user, { - [ENTITY_TYPE]: 'country', - [PRIMARY_KEY]: 'code', - code: 'us', - }) - }).toThrow( - 'Failed to resolve a "ONE_OF" relationship to "country" at "user.birthPlace" (id: "user-1"): referenced entity "country" (code: "us") does not exist.', - ) -}) - -it('throws an exception when resolving a unique relation that references an already references entity', () => { - const relation = new Relation({ - to: 'country', - kind: RelationKind.OneOf, - attributes: { - unique: true, - }, - }) - const dictionary: ModelDictionary = { - user: { - birthPlace: relation, - }, - country: { - code: primaryKey(String), - }, - } - const db = new Database(dictionary) - db.create('user', { - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'user-1', - }) - db.create('user', { - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'user-2', - }) - const firstUser = db.getModel('user').get('user-1')! - const secondUser = db.getModel('user').get('user-2')! - - const country = { - [ENTITY_TYPE]: 'country', - [PRIMARY_KEY]: 'code', - code: 'us', - } - db.create('country', country) - - // First, apply a new relation to the first user. - relation.apply(firstUser, ['birthPlace'], dictionary, db) - relation.resolveWith(firstUser, country) - - // Then, apply the relationship to the second user, - // referencing the same country (the relationship is unique). - expect(() => { - relation.resolveWith(secondUser, country) - }).toThrow( - 'Failed to resolve a "ONE_OF" relationship to "country" at "user.birthPlace" (id: "user-2"): the referenced "country" (code: "us") belongs to another "user" (id: "user-1").', - ) -}) - -it('does not throw an exception when updating the relational reference to the same reference', () => { - const relation = new Relation({ - to: 'country', - kind: RelationKind.OneOf, - attributes: { - unique: true, - }, - }) - const dictionary: ModelDictionary = { - user: { - birthPlace: relation, - }, - country: { - code: primaryKey(String), - }, - } - const db = new Database(dictionary) - const users = db.create('user', { - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'user-1', - }) - const user = users.get('user-1')! - - const countries = db.create('country', { - [ENTITY_TYPE]: 'country', - [PRIMARY_KEY]: 'code', - code: 'us', - }) - const country = countries.get('us') - - // First, apply and resolve a new relation for the first user. - relation.apply(user, ['birthPlace'], dictionary, db) - relation.resolveWith(user, { - [ENTITY_TYPE]: 'country', - [PRIMARY_KEY]: 'code', - code: 'us', - }) - - // Update the relational reference to the same referenced country. - relation.resolveWith(user, { - [ENTITY_TYPE]: 'country', - [PRIMARY_KEY]: 'code', - code: 'us', - }) - - expect(user).toHaveRelationalProperty('birthPlace', country) -}) - -it('supports creating nullable relations', () => { - const relation = new Relation({ - to: 'country', - kind: RelationKind.OneOf, - attributes: { - nullable: true, - }, - }) - - expect(relation.attributes.nullable).toBe(true) -}) - -it('throws an exception when resolving a non-nullable relation with null', () => { - const relation = new Relation({ - to: 'country', - kind: RelationKind.OneOf, - }) - const dictionary: ModelDictionary = { - user: { - birthPlace: relation, - }, - country: { - code: primaryKey(String), - }, - } - const db = new Database(dictionary) - db.create('user', { - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'user-1', - }) - const user = db.getModel('user').get('user-1')! - - const countries = db.create('country', { - [ENTITY_TYPE]: 'country', - [PRIMARY_KEY]: 'code', - code: 'us', - }) - const country = countries.get('us') - - // First, apply a new relation to the user. - relation.apply(user, ['birthPlace'], dictionary, db) - relation.resolveWith(user, { - [ENTITY_TYPE]: 'country', - [PRIMARY_KEY]: 'code', - code: 'us', - }) - - // Then, update the relational property to resolve with null. - expect(() => { - relation.resolveWith(user, null) - }).toThrow( - 'Failed to resolve a "ONE_OF" relationship to "country" at "user.birthPlace" (id: "user-1"): cannot resolve a non-nullable relationship with null.', - ) - - // The relational property still resolves with the previous value. - expect(user).toHaveRelationalProperty('birthPlace', country) -}) - -it('does not throw an exception when resolving a nullable relation with null', () => { - const relation = new Relation({ - to: 'country', - kind: RelationKind.OneOf, - attributes: { - nullable: true, - }, - }) - const dictionary: ModelDictionary = { - user: { - birthPlace: relation, - }, - country: { - code: primaryKey(String), - }, - } - const db = new Database(dictionary) - db.create('user', { - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'user-1', - }) - const user = db.getModel('user').get('user-1')! - - // First, apply a new relation to the user. - relation.apply(user, ['birthPlace'], dictionary, db) - - // Then, update the relational property to return null. - expect(() => { - relation.resolveWith(user, null) - }).not.toThrow() - - // The relational property now resolves to null. - expect(user).toHaveRelationalProperty('birthPlace', null) -}) diff --git a/test/relations/bi-directional.test.ts b/test/relations/bi-directional.test.ts deleted file mode 100644 index b00aa150..00000000 --- a/test/relations/bi-directional.test.ts +++ /dev/null @@ -1,444 +0,0 @@ -/** - * @see https://github.com/mswjs/data/issues/139 - */ -import { factory, manyOf, oneOf, primaryKey } from '@mswjs/data' - -test('supports creating a bi-directional one-to-one relationship', () => { - const db = factory({ - user: { - id: primaryKey(String), - partner: oneOf('user'), - }, - }) - - // Create a bi-directional relationship between partners. - const starsky = db.user.create({ - id: 'starsky', - }) - const hutch = db.user.create({ - id: 'hutch', - partner: starsky, - }) - const nextStarsky = db.user.update({ - where: { id: { equals: starsky.id } }, - data: { partner: hutch }, - strict: true, - })! - - expect(hutch.partner).toBe(nextStarsky) - expect(nextStarsky.partner).toBe(hutch) -}) - -test('supports creating a bi-directional one-to-many relationship', () => { - const db = factory({ - user: { - id: primaryKey(String), - posts: manyOf('post'), - }, - post: { - id: primaryKey(String), - title: String, - author: oneOf('user'), - }, - }) - - // Create a bi-directional relationship between author and posts. - const author = db.user.create({ id: 'user-1' }) - const posts = [ - db.post.create({ - id: 'post-1', - title: 'First post', - author, - }), - db.post.create({ - id: 'post-2', - title: 'Second post', - author, - }), - ] - const nextAuthor = db.user.update({ - where: { id: { equals: author.id } }, - data: { posts }, - strict: true, - })! - - posts.forEach((post) => { - expect(post.author).toBe(nextAuthor) - expect(nextAuthor.posts?.includes(post)).toBe(true) - }) -}) - -test('supports creating a bi-directional many-to-many relationship', () => { - const db = factory({ - user: { - id: primaryKey(String), - posts: manyOf('post'), - }, - post: { - id: primaryKey(String), - title: String, - authors: manyOf('user'), - }, - }) - - // Create a bi-directional relationship between authors and posts. - const authors = [ - db.user.create({ id: 'user-1' }), - db.user.create({ id: 'user-2' }), - ] - const posts = [ - db.post.create({ - id: 'post-1', - title: 'First post', - authors, - }), - db.post.create({ - id: 'post-2', - title: 'Second post', - authors, - }), - ] - const nextAuthors = db.user.updateMany({ - where: { id: { in: authors.map(({ id }) => id) } }, - data: { posts }, - strict: true, - })! - - expect(nextAuthors).toHaveLength(authors.length) - posts.forEach((post) => { - nextAuthors.forEach((author) => { - expect(post.authors?.includes(author)).toBe(true) - expect(author.posts?.includes(post)).toBe(true) - }) - }) -}) - -test('supports querying by a bi-directional one-to-one relationship', () => { - const db = factory({ - user: { - id: primaryKey(String), - partner: oneOf('user'), - }, - }) - - // Create a bi-directional relationship between partners - const starsky = db.user.create({ - id: 'starsky', - }) - const hutch = db.user.create({ - id: 'hutch', - partner: starsky, - }) - db.user.update({ - where: { id: { equals: starsky.id } }, - data: { partner: hutch }, - strict: true, - })! - - // Create an unrelated user to ensure they are not found - db.user.create({ id: 'user' }) - - // Can find models using bi-directional relationship - const starskysPartner = db.user.findFirst({ - where: { partner: { id: { equals: starsky.id } } }, - strict: true, - }) - - expect(starskysPartner).toBe(hutch) -}) - -test('supports querying by a bi-directional one-to-many relationship', () => { - const db = factory({ - user: { - id: primaryKey(String), - posts: manyOf('post'), - }, - post: { - id: primaryKey(String), - title: String, - author: oneOf('user'), - }, - }) - - // Create a bi-directional relationship between author and posts - const firstAuthor = db.user.create({ id: 'user-1' }) - const secondAuthor = db.user.create({ id: 'user-2' }) - const thirdAuthor = db.user.create({ id: 'user-3' }) - - const firstPost = db.post.create({ - id: 'post-1', - title: 'First post', - author: firstAuthor, - }) - const secondPost = db.post.create({ - id: 'post-2', - title: 'Second post', - author: firstAuthor, - }) - const thirdPost = db.post.create({ - id: 'post-3', - title: 'Third post', - author: secondAuthor, - }) - const fourthPost = db.post.create({ - id: 'post-4', - title: 'Fourth post', - author: thirdAuthor, - }) - const nextFirstAuthor = db.user.update({ - where: { id: { equals: firstAuthor.id } }, - data: { posts: [firstPost, secondPost] }, - strict: true, - })! - const nextSecondAuthor = db.user.update({ - where: { id: { equals: secondAuthor.id } }, - data: { posts: [thirdPost] }, - strict: true, - })! - db.user.update({ - where: { id: { equals: thirdAuthor.id } }, - data: { posts: [fourthPost] }, - strict: true, - }) - - // Create unrelated user and post to ensure they are not included. - db.user.create({ id: 'user-unrelated' }) - db.post.create({ id: 'post-unrelated' }) - - // Find posts in one-to-many direction - const posts = db.post.findMany({ - where: { author: { id: { in: [firstAuthor.id, secondAuthor.id] } } }, - strict: true, - })! - - const expectedPosts = [firstPost, secondPost, thirdPost] - expect(posts).toHaveLength(expectedPosts.length) - - expectedPosts.forEach((expectedPost) => { - expect(posts.includes(expectedPost)).toBe(true) - }) - - // Find authors in many-to-one direction. - const authors = db.user.findMany({ - where: { posts: { id: { in: [secondPost.id, thirdPost.id] } } }, - strict: true, - })! - - const expectedAuthors = [nextFirstAuthor, nextSecondAuthor] - expect(authors).toHaveLength(expectedAuthors.length) - - expectedAuthors.forEach((expectedAuthor) => { - expect(authors.includes(expectedAuthor)).toBe(true) - }) -}) - -test('supports querying by a bi-directional many-to-many relationship', () => { - const db = factory({ - user: { - id: primaryKey(String), - posts: manyOf('post'), - }, - post: { - id: primaryKey(String), - title: String, - authors: manyOf('user'), - }, - }) - - // Create a bi-directional relationship between authors and posts. - const firstAuthor = db.user.create({ id: 'user-1' }) - const secondAuthor = db.user.create({ id: 'user-2' }) - const thirdAuthor = db.user.create({ id: 'user-3' }) - - const firstPost = db.post.create({ - id: 'post-1', - title: 'First post', - authors: [firstAuthor], - }) - const secondPost = db.post.create({ - id: 'post-2', - title: 'Second post', - authors: [firstAuthor, secondAuthor], - }) - const thirdPost = db.post.create({ - id: 'post-3', - title: 'Third post', - authors: [secondAuthor, thirdAuthor], - }) - const fourthPost = db.post.create({ - id: 'post-4', - title: 'Fourth post', - authors: [thirdAuthor], - }) - db.user.update({ - where: { id: { equals: firstAuthor.id } }, - data: { posts: [firstPost, secondPost] }, - strict: true, - }) - db.user.update({ - where: { id: { equals: secondAuthor.id } }, - data: { posts: [thirdPost] }, - strict: true, - }) - db.user.update({ - where: { id: { equals: thirdAuthor.id } }, - data: { posts: [fourthPost] }, - strict: true, - }) - - // Find posts through many-to-many relationship. - const posts = db.post.findMany({ - where: { authors: { id: { in: [firstAuthor.id, secondAuthor.id] } } }, - strict: true, - }) - - const expectedPosts = [firstPost, secondPost, thirdPost] - expect(posts).toHaveLength(expectedPosts.length) - - expectedPosts.forEach((expectedPost) => { - expect(posts.includes(expectedPost)).toBe(true) - }) -}) - -test('supports updating using an entity with a bi-directional one-to-one relation', () => { - const db = factory({ - user: { - id: primaryKey(String), - partner: oneOf('user'), - }, - }) - - // Create a bi-directional relationship between partners. - const hutch = db.user.create({ - id: 'hutch', - partner: db.user.create({ - id: 'starsky', - }), - }) - const starsky = db.user.update({ - where: { id: { equals: 'starsky' } }, - data: { partner: hutch }, - strict: true, - })! - - // Create a user that is not related to starsky or hutch. - db.user.create({ id: 'user' }) - - // Update using user with bi-directional relationship. - const user = db.user.update({ - where: { id: { equals: 'user' } }, - data: { partner: starsky }, - strict: true, - })! - - expect(user.partner).toBe(starsky) -}) - -test('supports updating using an entity with a bi-directional one-to-many relationship', () => { - const db = factory({ - user: { - id: primaryKey(String), - posts: manyOf('post'), - }, - post: { - id: primaryKey(String), - title: String, - author: oneOf('user'), - }, - }) - - // Create a bi-directional relationship between author and posts. - const author = db.user.create({ - id: 'user-1', - }) - const posts = [ - db.post.create({ - id: 'post-1', - title: 'First post', - author, - }), - db.post.create({ - id: 'post-2', - title: 'Second post', - author, - }), - ] - const nextAuthor = db.user.update({ - where: { id: { equals: author.id } }, - data: { posts }, - strict: true, - })! - - // Create a post that is not related to author. - db.post.create({ - id: 'post-3', - title: 'Third post', - }) - - // Update post using author with bi-directional relationship. - const post = db.post.update({ - where: { id: { equals: 'post-3' } }, - data: { author: nextAuthor }, - strict: true, - })! - - expect(post.author).toBe(nextAuthor) -}) - -test('supports updating using an entity with a bi-directional many-to-many relation', () => { - const db = factory({ - user: { - id: primaryKey(String), - posts: manyOf('post'), - }, - post: { - id: primaryKey(String), - title: String, - authors: manyOf('user'), - }, - }) - - // Create a bi-directional relationship between authors and posts. - const authors = [ - db.user.create({ - id: 'user-1', - }), - db.user.create({ - id: 'user-2', - }), - ] - const posts = [ - db.post.create({ - id: 'post-1', - title: 'First post', - authors, - }), - db.post.create({ - id: 'post-2', - title: 'Second post', - authors, - }), - ] - const nextAuthors = db.user.updateMany({ - where: { id: { in: authors.map((author) => author.id) } }, - data: { posts }, - strict: true, - })! - - // Create an unrelated post. - db.post.create({ - id: 'post-3', - }) - - // Update post using authors with bi-directional relationships. - const post = db.post.update({ - where: { id: { equals: 'post-2' } }, - data: { authors: nextAuthors }, - strict: true, - })! - - expect(post.authors).toHaveLength(authors.length) - post.authors?.forEach((author, i) => { - expect(author).toBe(nextAuthors[i]) - }) -}) diff --git a/test/relations/many-to-one.test.ts b/test/relations/many-to-one.test.ts deleted file mode 100644 index 4adef6b5..00000000 --- a/test/relations/many-to-one.test.ts +++ /dev/null @@ -1,468 +0,0 @@ -import { factory, oneOf, primaryKey, nullable } from '../../src' -import { ENTITY_TYPE, PRIMARY_KEY } from '../../src/glossary' - -test('supports querying by a many-to-one relation', () => { - const db = factory({ - user: { - id: primaryKey(String), - }, - post: { - id: primaryKey(String), - title: String, - author: oneOf('user'), - }, - }) - - const author = db.user.create({ - id: 'user-1', - }) - - db.post.create({ - id: 'post-1', - title: 'First post', - author, - }) - db.post.create({ - id: 'post-2', - title: 'Second post', - author, - }) - db.post.create({ - id: 'post-3', - title: 'Third post', - author, - }) - - const userPosts = db.post.findMany({ - where: { - author: { - id: { - equals: author.id, - }, - }, - }, - }) - - expect(userPosts).toEqual([ - { - [ENTITY_TYPE]: 'post', - [PRIMARY_KEY]: 'id', - id: 'post-1', - title: 'First post', - author: author, - }, - { - [ENTITY_TYPE]: 'post', - [PRIMARY_KEY]: 'id', - id: 'post-2', - title: 'Second post', - author: author, - }, - { - [ENTITY_TYPE]: 'post', - [PRIMARY_KEY]: 'id', - id: 'post-3', - title: 'Third post', - author: author, - }, - ]) -}) - -test('supports querying by a nullable many-to-one relation', () => { - const db = factory({ - user: { - id: primaryKey(String), - }, - post: { - id: primaryKey(String), - title: String, - author: nullable(oneOf('user')), - }, - }) - - const author = db.user.create({ - id: 'user-1', - }) - - db.post.create({ - id: 'post-1', - title: 'First post', - author, - }) - db.post.create({ - id: 'post-2', - title: 'Second post', - author, - }) - db.post.create({ - id: 'post-3', - title: 'Third post', - author, - }) - db.post.create({ - id: 'post-4', - title: 'Fourth post', - }) - - const userPosts = db.post.findMany({ - where: { - author: { - id: { - equals: author.id, - }, - }, - }, - }) - - expect(userPosts).toEqual([ - { - [ENTITY_TYPE]: 'post', - [PRIMARY_KEY]: 'id', - id: 'post-1', - title: 'First post', - author: author, - }, - { - [ENTITY_TYPE]: 'post', - [PRIMARY_KEY]: 'id', - id: 'post-2', - title: 'Second post', - author: author, - }, - { - [ENTITY_TYPE]: 'post', - [PRIMARY_KEY]: 'id', - id: 'post-3', - title: 'Third post', - author: author, - }, - ]) -}) - -test('supports querying by a nested many-to-one relation', () => { - const db = factory({ - role: { - name: primaryKey(String), - }, - user: { - id: primaryKey(String), - role: oneOf('role'), - }, - post: { - id: primaryKey(String), - author: oneOf('user'), - }, - }) - - const editor = db.role.create({ - name: 'editor', - }) - const reader = db.role.create({ - name: 'reader', - }) - - const john = db.user.create({ - id: 'john', - role: editor, - }) - const kate = db.user.create({ - id: 'kate', - role: reader, - }) - db.user.create({ - id: 'joseph', - role: reader, - }) - - db.post.create({ - id: 'post-1', - author: john, - }) - db.post.create({ - id: 'post-2', - author: john, - }) - db.post.create({ - id: 'post-3', - author: kate, - }) - db.post.create({ - id: 'post-4', - author: john, - }) - - const posts = db.post.findMany({ - where: { - author: { - role: { - name: { - equals: 'editor', - }, - }, - }, - }, - }) - - expect(posts).toEqual([ - { - [ENTITY_TYPE]: 'post', - [PRIMARY_KEY]: 'id', - id: 'post-1', - author: john, - }, - { - [ENTITY_TYPE]: 'post', - [PRIMARY_KEY]: 'id', - id: 'post-2', - author: john, - }, - { - [ENTITY_TYPE]: 'post', - [PRIMARY_KEY]: 'id', - id: 'post-4', - author: john, - }, - ]) -}) - -test('supports querying by a nested nullable many-to-one relation', () => { - const db = factory({ - role: { - name: primaryKey(String), - }, - user: { - id: primaryKey(String), - role: nullable(oneOf('role')), - }, - post: { - id: primaryKey(String), - author: oneOf('user'), - }, - }) - - const editor = db.role.create({ - name: 'editor', - }) - const reader = db.role.create({ - name: 'reader', - }) - - const john = db.user.create({ - id: 'john', - role: editor, - }) - const kate = db.user.create({ - id: 'kate', - role: reader, - }) - const guest = db.user.create({ - id: 'guest', - }) - db.user.create({ - id: 'joseph', - role: reader, - }) - - db.post.create({ - id: 'post-1', - author: john, - }) - db.post.create({ - id: 'post-2', - author: john, - }) - db.post.create({ - id: 'post-3', - author: kate, - }) - db.post.create({ - id: 'post-4', - author: john, - }) - db.post.create({ - id: 'post-5', - author: guest, - }) - - const posts = db.post.findMany({ - where: { - author: { - role: { - name: { - equals: 'editor', - }, - }, - }, - }, - }) - - expect(posts).toEqual([ - { - [ENTITY_TYPE]: 'post', - [PRIMARY_KEY]: 'id', - id: 'post-1', - author: john, - }, - { - [ENTITY_TYPE]: 'post', - [PRIMARY_KEY]: 'id', - id: 'post-2', - author: john, - }, - { - [ENTITY_TYPE]: 'post', - [PRIMARY_KEY]: 'id', - id: 'post-4', - author: john, - }, - ]) -}) - -test('updates a many-to-one relational property without initial value', () => { - const db = factory({ - user: { - id: primaryKey(String), - }, - post: { - id: primaryKey(String), - author: oneOf('user'), - }, - }) - - db.post.create({ - id: 'post-1', - }) - - const updatedPost = db.post.update({ - where: { - id: { - equals: 'post-1', - }, - }, - data: { - author: db.user.create({ id: 'john' }), - }, - }) - - expect(updatedPost).toEqual({ - [ENTITY_TYPE]: 'post', - [PRIMARY_KEY]: 'id', - id: 'post-1', - author: { - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'john', - }, - }) - - expect( - db.post.findFirst({ - where: { - id: { - equals: 'post-1', - }, - }, - }), - ).toEqual(updatedPost) -}) - -test('updates a nullable many-to-one relational property without initial value', () => { - const db = factory({ - user: { - id: primaryKey(String), - }, - post: { - id: primaryKey(String), - author: nullable(oneOf('user')), - }, - }) - - db.post.create({ - id: 'post-1', - }) - - const updatedPost = db.post.update({ - where: { - id: { - equals: 'post-1', - }, - }, - data: { - author: db.user.create({ id: 'john' }), - }, - }) - - expect(updatedPost).toEqual({ - [ENTITY_TYPE]: 'post', - [PRIMARY_KEY]: 'id', - id: 'post-1', - author: { - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'john', - }, - }) - - expect( - db.post.findFirst({ - where: { - id: { - equals: 'post-1', - }, - }, - }), - ).toEqual(updatedPost) -}) - -test('does not throw any error when a many-to-one entity is created without a relation', () => { - const db = factory({ - user: { - id: primaryKey(String), - firstName: String, - }, - post: { - id: primaryKey(String), - title: String, - author: oneOf('user'), - }, - }) - - const post = db.post.create({ - id: 'post-1', - title: 'First post', - }) - - expect(post).toEqual({ - [ENTITY_TYPE]: 'post', - [PRIMARY_KEY]: 'id', - id: 'post-1', - title: 'First post', - }) -}) - -test('does not throw any error when a nullable many-to-one entity is created without a relation', () => { - const db = factory({ - user: { - id: primaryKey(String), - firstName: String, - }, - post: { - id: primaryKey(String), - title: String, - author: nullable(oneOf('user')), - }, - }) - - const post = db.post.create({ - id: 'post-1', - title: 'First post', - }) - - expect(post).toEqual({ - [ENTITY_TYPE]: 'post', - [PRIMARY_KEY]: 'id', - id: 'post-1', - title: 'First post', - author: null, - }) -}) diff --git a/test/relations/one-to-many.test.ts b/test/relations/one-to-many.test.ts deleted file mode 100644 index 67bf4ba9..00000000 --- a/test/relations/one-to-many.test.ts +++ /dev/null @@ -1,969 +0,0 @@ -import { factory, primaryKey, manyOf, nullable } from '../../src' -import { ENTITY_TYPE, PRIMARY_KEY } from '../../src/glossary' - -test('supports one-to-many relation', () => { - const db = factory({ - user: { - id: primaryKey(String), - posts: manyOf('post'), - }, - post: { - id: primaryKey(String), - title: String, - }, - }) - - const firstPost = db.post.create({ - id: 'post-1', - title: 'First post', - }) - const secondPost = db.post.create({ - id: 'post-2', - title: 'Second post', - }) - const user = db.user.create({ - id: 'user-1', - posts: [firstPost, secondPost], - }) - - expect(user.posts).toEqual([firstPost, secondPost]) - - expect( - db.user.findFirst({ - where: { - id: { - equals: 'user-1', - }, - }, - }), - ).toEqual(user) -}) - -test('returns an empty array for a non-nullable one-to-many relation with no value', () => { - const db = factory({ - user: { - id: primaryKey(String), - posts: manyOf('post'), - }, - post: { - id: primaryKey(String), - title: String, - }, - }) - - const user = db.user.create({ - id: 'user-1', - }) - - // Non-nullable "manyOf" relations return an empty array. - expect(user.posts).toEqual([]) -}) - -test('returns null for a nullable one-to-many relation with no value', () => { - const db = factory({ - user: { - id: primaryKey(String), - posts: nullable(manyOf('post')), - }, - post: { - id: primaryKey(String), - title: String, - }, - }) - - const user = db.user.create({ - id: 'user-1', - }) - - // Nullable "manyOf" relations return null. - expect(user.posts).toBeNull() -}) - -test('supports nullable one-to-many relation', () => { - const db = factory({ - user: { - id: primaryKey(String), - posts: nullable(manyOf('post')), - }, - post: { - id: primaryKey(String), - title: String, - }, - }) - - const firstPost = db.post.create({ - id: 'post-1', - title: 'First post', - }) - const secondPost = db.post.create({ - id: 'post-2', - title: 'Second post', - }) - const user = db.user.create({ - id: 'user-1', - posts: [firstPost, secondPost], - }) - - expect(user.posts).toEqual([firstPost, secondPost]) - - expect( - db.user.findFirst({ - where: { - id: { - equals: 'user-1', - }, - }, - }), - ).toEqual(user) - - const postlessUser = db.user.create({ - id: 'user-2', - }) - expect(postlessUser.posts).toBeNull() - - expect( - db.user.findFirst({ - where: { - id: { - equals: 'user-2', - }, - }, - }), - ).toEqual(postlessUser) -}) - -test('supports updating a recursive one-to-many relation', () => { - const db = factory({ - user: { - id: primaryKey(String), - firstName: String, - friends: manyOf('user'), - }, - }) - - const john = db.user.create({ - id: 'john', - firstName: 'John', - friends: [], - }) - - const kate = db.user.create({ - id: 'kate', - firstName: 'Kate', - friends: [john], - }) - - db.user.findFirst({ - where: { id: { equals: 'john' } }, - strict: true, - }) - - const updatedJohn = db.user.update({ - where: { - id: { - equals: john.id, - }, - }, - data: { - friends: [kate], - }, - strict: true, - })! - - expect(updatedJohn.friends).toHaveLength(1) - expect(updatedJohn.friends![0]?.firstName).toEqual('Kate') -}) - -test('supports updating a recursive nullable one-to-many relation', () => { - const db = factory({ - user: { - id: primaryKey(String), - firstName: String, - friends: nullable(manyOf('user')), - }, - }) - - const john = db.user.create({ - id: 'john', - firstName: 'John', - friends: [], - }) - - const kate = db.user.create({ - id: 'kate', - firstName: 'Kate', - friends: [john], - }) - - db.user.findFirst({ - where: { id: { equals: 'john' } }, - strict: true, - }) - - const updatedJohn = db.user.update({ - where: { - id: { - equals: john.id, - }, - }, - data: { - friends: [kate], - }, - strict: true, - })! - - expect(updatedJohn.friends).toHaveLength(1) - expect(updatedJohn.friends?.shift()?.firstName).toEqual('Kate') - - const jack = db.user.create({ - id: 'jack', - firstName: 'Jack', - friends: null, - }) - - expect(jack.friends).toBeNull() - - const updatedJack = db.user.update({ - where: { - id: { - equals: john.id, - }, - }, - data: { - friends: [john], - }, - strict: true, - })! - - expect(updatedJack.friends).toHaveLength(1) - expect(updatedJack.friends?.shift()?.firstName).toEqual('John') -}) - -test('supports querying through one-to-many relation', () => { - const db = factory({ - user: { - id: primaryKey(String), - posts: manyOf('post'), - }, - post: { - id: primaryKey(String), - title: String, - }, - }) - - const firstUserPosts = [ - db.post.create({ - id: 'post-1-1', - title: 'First post', - }), - db.post.create({ - id: 'post-1-2', - title: 'Second post', - }), - ] - - db.user.create({ - id: 'user-1', - posts: firstUserPosts, - }) - - db.user.create({ - id: 'user-2', - posts: [ - db.post.create({ - id: 'post-2-1', - title: 'Third post', - }), - ], - }) - - const thirdUserPosts = [ - db.post.create({ id: 'post-3-1', title: 'Second post' }), - db.post.create({ id: 'post-3-2', title: 'Fourth post' }), - ] - db.user.create({ - id: 'user-3', - posts: thirdUserPosts, - }) - - const users = db.user.findMany({ - where: { - posts: { - title: { - in: ['First post', 'Second post'], - }, - }, - }, - }) - - expect(users).toEqual([ - { - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'user-1', - posts: firstUserPosts, - }, - { - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'user-3', - posts: thirdUserPosts, - }, - ]) -}) - -test('supports querying through nullable one-to-many relation', () => { - const db = factory({ - user: { - id: primaryKey(String), - posts: nullable(manyOf('post')), - }, - post: { - id: primaryKey(String), - title: String, - }, - }) - - const firstUserPosts = [ - db.post.create({ - id: 'post-1-1', - title: 'First post', - }), - db.post.create({ - id: 'post-1-2', - title: 'Second post', - }), - ] - - db.user.create({ - id: 'user-1', - posts: firstUserPosts, - }) - - db.user.create({ - id: 'user-2', - posts: [ - db.post.create({ - id: 'post-2-1', - title: 'Third post', - }), - ], - }) - - const thirdUserPosts = [ - db.post.create({ id: 'post-3-1', title: 'Second post' }), - db.post.create({ id: 'post-3-2', title: 'Fourth post' }), - ] - db.user.create({ - id: 'user-3', - posts: thirdUserPosts, - }) - - db.user.create({ - id: 'user-4', - posts: null, - }) - - const users = db.user.findMany({ - where: { - posts: { - title: { - in: ['First post', 'Second post'], - }, - }, - }, - }) - - expect(users).toEqual([ - { - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'user-1', - posts: firstUserPosts, - }, - { - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'user-3', - posts: thirdUserPosts, - }, - ]) -}) - -test('supports querying through a nested one-to-many relation', () => { - const db = factory({ - user: { - id: primaryKey(String), - activity: { - posts: manyOf('post'), - }, - }, - post: { - id: primaryKey(String), - }, - }) - - const user = db.user.create({ - id: 'user-1', - activity: { - posts: [ - db.post.create({ id: 'post-1' }), - db.post.create({ id: 'post-2' }), - ], - }, - }) - - const result = db.user.findFirst({ - where: { - activity: { - posts: { - id: { - equals: 'post-2', - }, - }, - }, - }, - }) - - expect(result).toEqual(user) -}) - -test('supports querying through a nested nullable one-to-many relation', () => { - const db = factory({ - user: { - id: primaryKey(String), - activity: { - posts: nullable(manyOf('post')), - }, - }, - post: { - id: primaryKey(String), - }, - }) - - const user = db.user.create({ - id: 'user-1', - activity: { - posts: [ - db.post.create({ id: 'post-1' }), - db.post.create({ id: 'post-2' }), - ], - }, - }) - - const result = db.user.findFirst({ - where: { - activity: { - posts: { - id: { - equals: 'post-2', - }, - }, - }, - }, - }) - - expect(result).toEqual(user) -}) - -test('supports creating an entity without specifying the value for one-to-many relation', () => { - const db = factory({ - user: { - id: primaryKey(String), - posts: manyOf('post'), - }, - post: { - id: primaryKey(String), - }, - }) - - const user = db.user.create({ - id: 'abc-123', - }) - - expect( - db.user.findFirst({ - where: { - id: { - equals: 'abc-123', - }, - }, - }), - ).toEqual(user) -}) - -test('supports creating an entity without specifying the value for a nullable one-to-many relation', () => { - const db = factory({ - user: { - id: primaryKey(String), - posts: nullable(manyOf('post')), - }, - post: { - id: primaryKey(String), - }, - }) - - const user = db.user.create({ - id: 'abc-123', - }) - - expect( - db.user.findFirst({ - where: { - id: { - equals: 'abc-123', - }, - }, - }), - ).toEqual(user) -}) - -test('updates a one-to-many relational property', () => { - const db = factory({ - user: { - id: primaryKey(String), - posts: manyOf('post'), - }, - post: { - id: primaryKey(String), - title: String, - }, - }) - - const firstPost = db.post.create({ - id: 'post-1', - title: 'First post', - }) - const secondPost = db.post.create({ - id: 'post-2', - title: 'Second post', - }) - const user = db.user.create({ - id: 'abc-123', - posts: [firstPost], - }) - const refetchUser = () => { - return db.user.findFirst({ - where: { - id: { - equals: 'abc-123', - }, - }, - }) - } - - expect(user.posts).toEqual([firstPost]) - expect(refetchUser()).toEqual({ - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'abc-123', - posts: [firstPost], - }) - - // Update the "posts" relational property. - const updatedUser = db.user.update({ - where: { - id: { equals: 'abc-123' }, - }, - data: { - posts: [secondPost], - }, - }) - - expect(updatedUser).toEqual({ - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'abc-123', - posts: [secondPost], - }) - expect(refetchUser()).toEqual({ - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'abc-123', - posts: [secondPost], - }) -}) - -test('updates a nullable one-to-many relational property', () => { - const db = factory({ - user: { - id: primaryKey(String), - posts: nullable(manyOf('post')), - }, - post: { - id: primaryKey(String), - title: String, - }, - }) - - const firstPost = db.post.create({ - id: 'post-1', - title: 'First post', - }) - const secondPost = db.post.create({ - id: 'post-2', - title: 'Second post', - }) - const user = db.user.create({ - id: 'abc-123', - posts: [firstPost], - }) - const refetchUser = () => { - return db.user.findFirst({ - where: { - id: { - equals: 'abc-123', - }, - }, - }) - } - - expect(user.posts).toEqual([firstPost]) - expect(refetchUser()).toEqual({ - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'abc-123', - posts: [firstPost], - }) - - // Update the "posts" relational property. - let updatedUser = db.user.update({ - where: { - id: { equals: 'abc-123' }, - }, - data: { - posts: [secondPost], - }, - }) - - expect(updatedUser).toEqual({ - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'abc-123', - posts: [secondPost], - }) - expect(refetchUser()).toEqual({ - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'abc-123', - posts: [secondPost], - }) - - // Update the "posts" relational property to null. - updatedUser = db.user.update({ - where: { - id: { equals: 'abc-123' }, - }, - data: { - posts: null, - }, - }) - - expect(updatedUser).toEqual({ - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'abc-123', - posts: null, - }) - expect(refetchUser()).toEqual({ - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'abc-123', - posts: null, - }) -}) - -test('updates a one-to-many relational property without initial value', () => { - const db = factory({ - user: { - id: primaryKey(String), - posts: manyOf('post'), - }, - post: { - id: primaryKey(String), - title: String, - }, - }) - - db.user.create({ - id: 'user-1', - }) - - const updatedUser = db.user.update({ - where: { - id: { - equals: 'user-1', - }, - }, - data: { - posts: [ - db.post.create({ id: 'post-1', title: 'First post' }), - db.post.create({ id: 'post-2', title: 'Second post' }), - ], - }, - }) - - expect(updatedUser).toEqual({ - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'user-1', - posts: [ - { - [ENTITY_TYPE]: 'post', - [PRIMARY_KEY]: 'id', - id: 'post-1', - title: 'First post', - }, - { - [ENTITY_TYPE]: 'post', - [PRIMARY_KEY]: 'id', - id: 'post-2', - title: 'Second post', - }, - ], - }) - - expect( - db.user.findFirst({ - where: { - id: { - equals: 'user-1', - }, - }, - }), - ).toEqual(updatedUser) -}) - -test('updates a nullable one-to-many relational property without initial value', () => { - const db = factory({ - user: { - id: primaryKey(String), - posts: nullable(manyOf('post')), - }, - post: { - id: primaryKey(String), - title: String, - }, - }) - - db.user.create({ - id: 'user-1', - }) - - const updatedUser = db.user.update({ - where: { - id: { - equals: 'user-1', - }, - }, - data: { - posts: [ - db.post.create({ id: 'post-1', title: 'First post' }), - db.post.create({ id: 'post-2', title: 'Second post' }), - ], - }, - }) - - expect(updatedUser).toEqual({ - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'user-1', - posts: [ - { - [ENTITY_TYPE]: 'post', - [PRIMARY_KEY]: 'id', - id: 'post-1', - title: 'First post', - }, - { - [ENTITY_TYPE]: 'post', - [PRIMARY_KEY]: 'id', - id: 'post-2', - title: 'Second post', - }, - ], - }) - - expect( - db.user.findFirst({ - where: { - id: { - equals: 'user-1', - }, - }, - }), - ).toEqual(updatedUser) -}) - -test('throws an exception when updating a relational value via a compatible object', () => { - const db = factory({ - user: { - id: primaryKey(String), - posts: manyOf('post'), - }, - post: { - id: primaryKey(String), - title: String, - }, - }) - const firstPost = db.post.create({ - id: 'post-1', - title: 'First post', - }) - const user = db.user.create({ - id: 'abc-123', - posts: [firstPost], - }) - const refetchUser = () => { - return db.user.findFirst({ - where: { - id: { - equals: 'abc-123', - }, - }, - }) - } - - expect(user.posts).toEqual([firstPost]) - expect(refetchUser()).toEqual({ - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'abc-123', - posts: [firstPost], - }) - - expect(() => - db.user.update({ - where: { - id: { - equals: 'abc-123', - }, - }, - data: { - posts: [ - { - id: 'post-2', - title: 'Compatible object', - }, - ], - }, - }), - ).toThrow( - 'Failed to update a "MANY_OF" relationship to "post" at "user.posts" (id: "abc-123"): expected the next value at index 0 to be an entity but got {"id":"post-2","title":"Compatible object"}.', - ) -}) - -test('throws an exception when creating a unique one-to-many relation to the already referenced entity', () => { - const db = factory({ - user: { - id: primaryKey(String), - // One post cannot belong to multiple users. - posts: manyOf('post', { unique: true }), - }, - post: { - id: primaryKey(String), - }, - }) - - const post = db.post.create({ - id: 'post-1', - }) - - db.user.create({ - id: 'user-1', - posts: [post], - }) - - expect(() => - db.user.create({ - id: 'user-2', - posts: [post], - }), - ).toThrow( - 'Failed to resolve a "MANY_OF" relationship to "post" at "user.posts" (id: "user-2"): the referenced "post" (id: "post-1") belongs to another "user" (id: "user-1").', - ) -}) - -test('throws an exception when updating a unique one-to-many relation to the already referenced entity', () => { - const db = factory({ - user: { - id: primaryKey(String), - // One post cannot belong to multiple users. - posts: manyOf('post', { unique: true }), - }, - post: { - id: primaryKey(String), - }, - }) - - const post = db.post.create({ - id: 'post-1', - }) - - db.user.create({ - id: 'user-1', - posts: [post], - }) - - db.user.create({ - id: 'user-2', - }) - - expect(() => - db.user.update({ - where: { - id: { - equals: 'user-2', - }, - }, - data: { - posts: [post], - }, - strict: true, - }), - ).toThrow( - 'Failed to resolve a "MANY_OF" relationship to "post" at "user.posts" (id: "user-2"): the referenced "post" (id: "post-1") belongs to another "user" (id: "user-1").', - ) -}) - -test('throws an exception when updating a non-nullable one-to-many relation to null', () => { - const db = factory({ - user: { - id: primaryKey(String), - posts: manyOf('post'), - }, - post: { - id: primaryKey(String), - }, - }) - - const post = db.post.create({ - id: 'post-1', - }) - - db.user.create({ - id: 'user-1', - posts: [post], - }) - - db.user.create({ - id: 'user-2', - }) - - expect(() => - db.user.update({ - where: { - id: { - equals: 'user-2', - }, - }, - data: { - // @ts-expect-error updating non-nullable relation to null not allowed - posts: null, - }, - strict: true, - }), - ).toThrow( - 'Failed to update a "MANY_OF" relationship to "post" at "user.posts" (id: "user-2"): cannot update a non-nullable relationship to null.', - ) -}) diff --git a/test/relations/one-to-one.create.test.ts b/test/relations/one-to-one.create.test.ts deleted file mode 100644 index d4e3f0bf..00000000 --- a/test/relations/one-to-one.create.test.ts +++ /dev/null @@ -1,407 +0,0 @@ -import { nullable, oneOf, primaryKey } from '../../src' -import { testFactory } from '../testUtils' - -/** - * Nullable one-to-one relationship. - */ -it('creates a nullable relationship with entity as initial value', () => { - const { db, entity } = testFactory({ - country: { - code: primaryKey(String), - capital: nullable(oneOf('city')), - }, - city: { - name: primaryKey(String), - }, - }) - - const country = db.country.create({ - code: 'uk', - capital: db.city.create({ - name: 'London', - }), - }) - - const expectedCountry = entity('country', { - code: 'uk', - capital: entity('city', { - name: 'London', - }), - }) - - expect(country).toHaveRelationalProperty( - 'capital', - entity('city', { name: 'London' }), - ) - - expect(country).toEqual(expectedCountry) - expect(db.country.findFirst({ where: { code: { equals: 'uk' } } })).toEqual( - expectedCountry, - ) - expect( - db.country.findFirst({ - where: { capital: { name: { equals: 'London' } } }, - }), - ).toEqual(expectedCountry) -}) - -it('creates a nullable relationship with null as initial value', () => { - const { db, entity } = testFactory({ - country: { - code: primaryKey(String), - capital: nullable(oneOf('city')), - }, - city: { - name: primaryKey(String), - }, - }) - - const country = db.country.create({ - code: 'uk', - capital: null, - }) - - const expectedCountry = entity('country', { - code: 'uk', - capital: null, - }) - - expect(country).toHaveRelationalProperty('capital', null) - - expect(country).toEqual(expectedCountry) - expect(db.country.findFirst({ where: { code: { equals: 'uk' } } })).toEqual( - expectedCountry, - ) - - expect(db.city.count()).toEqual(0) -}) - -it('creates a nullable relationship without initial value', () => { - const { db, entity } = testFactory({ - country: { - code: primaryKey(String), - capital: nullable(oneOf('city')), - }, - city: { - name: primaryKey(String), - }, - }) - - const country = db.country.create({ - code: 'uk', - }) - - const expectedCountry = entity('country', { - code: 'uk', - // Nullable relational property is set to null by default. - capital: null, - }) - - expect(country).toEqual(expectedCountry) - expect(db.country.findFirst({ where: { code: { equals: 'uk' } } })).toEqual( - expectedCountry, - ) -}) - -it('forbids creating a nullable relationship referencing a different model', () => { - const { db } = testFactory({ - country: { - code: primaryKey(String), - capital: nullable(oneOf('city')), - }, - city: { - name: primaryKey(String), - }, - user: { - id: primaryKey(String), - }, - }) - - expect(() => - db.country.create({ - code: 'uk', - // @ts-expect-error Runtime value incompatibility. - capital: db.user.create({ id: 'user-1' }), - }), - ).toThrow( - 'Failed to resolve a "ONE_OF" relationship to "city" at "country.capital" (code: "uk"): expected a referenced entity to be "city" but got "user" (id: "user-1").', - ) - expect(db.country.count()).toEqual(0) -}) - -it('creates a nullable unique relationship with initial value', () => { - const { db, entity } = testFactory({ - country: { - code: primaryKey(String), - capital: nullable(oneOf('city', { unique: true })), - }, - city: { - name: primaryKey(String), - }, - }) - - const london = db.city.create({ name: 'London' }) - const country = db.country.create({ - code: 'uk', - capital: london, - }) - - const expectedCountry = entity('country', { - code: 'uk', - capital: london, - }) - - expect(country).toHaveRelationalProperty('capital', london) - - expect(country).toEqual(expectedCountry) - expect(db.country.findFirst({ where: { code: { equals: 'uk' } } })).toEqual( - expectedCountry, - ) - expect( - db.country.findFirst({ - where: { capital: { name: { equals: 'London' } } }, - }), - ).toEqual(expectedCountry) -}) - -it('creates a nullable unique relationship with null as initial value', () => { - const { db, entity } = testFactory({ - country: { - code: primaryKey(String), - capital: nullable(oneOf('city', { unique: true })), - }, - city: { - name: primaryKey(String), - }, - }) - - const country = db.country.create({ - code: 'uk', - capital: null, - }) - - const expectedCountry = entity('country', { - code: 'uk', - capital: null, - }) - - expect(country).toHaveRelationalProperty('capital', null) - - expect(country).toEqual(expectedCountry) - expect(db.country.findFirst({ where: { code: { equals: 'uk' } } })).toEqual( - expectedCountry, - ) -}) - -/** - * Non-nullable one-to-one relationship. - */ -it('creates a non-nullable relationship', () => { - const { db, entity } = testFactory({ - country: { - code: primaryKey(String), - capital: oneOf('city'), - }, - city: { - name: primaryKey(String), - }, - }) - - const london = db.city.create({ name: 'London' }) - const country = db.country.create({ - code: 'uk', - capital: london, - }) - - const expectedCountry = entity('country', { - code: 'uk', - capital: london, - }) - - expect(country).toHaveRelationalProperty('capital', london) - - expect(country).toEqual(expectedCountry) - expect(db.country.findFirst({ where: { code: { equals: 'uk' } } })).toEqual( - expectedCountry, - ) - expect( - db.country.findFirst({ - where: { capital: { name: { equals: 'London' } } }, - }), - ).toEqual(expectedCountry) -}) - -it('creates a non-nullable relationship without the initial value', () => { - const { db, entity } = testFactory({ - country: { - code: primaryKey(String), - capital: oneOf('city'), - }, - city: { - name: primaryKey(String), - }, - }) - - const country = db.country.create({ - code: 'uk', - }) - - const expectedCountry = entity('country', { - code: 'uk', - capital: undefined, - }) - - expect(country).toEqual(expectedCountry) - expect(db.country.findFirst({ where: { code: { equals: 'uk' } } })).toEqual( - expectedCountry, - ) - expect( - db.country.findFirst({ - where: { capital: { name: { equals: 'Manchester' } } }, - }), - ).toEqual(null) -}) - -it('forbids creating a non-nullable relationship with null as initial value', () => { - const { db } = testFactory({ - country: { - code: primaryKey(String), - capital: oneOf('city'), - }, - city: { - name: primaryKey(String), - }, - }) - - expect(() => - db.country.create({ - code: 'uk', - // @ts-expect-error Runtime value incompatibility. - capital: null, - }), - ).toThrow( - 'Failed to define a "ONE_OF" relationship to "city" at "country.capital" (code: "uk"): cannot set a non-nullable relationship to null.', - ) -}) - -it('forbids creating a non-nullable relatiosnhip referencing a different model', () => { - const { db, entity } = testFactory({ - country: { - code: primaryKey(String), - capital: oneOf('city'), - }, - city: { - name: primaryKey(String), - }, - user: { - id: primaryKey(String), - }, - }) - - expect(() => - db.country.create({ - code: 'uk', - // @ts-expect-error Runtime value incompatibility. - capital: db.user.create({ id: 'user-1' }), - }), - ).toThrow( - 'Failed to resolve a "ONE_OF" relationship to "city" at "country.capital" (code: "uk"): expected a referenced entity to be "city" but got "user" (id: "user-1")', - ) - - expect(db.country.count()).toEqual(0) - expect(db.user.getAll()).toEqual([entity('user', { id: 'user-1' })]) -}) - -/** - * Unique relationship. - */ -it('creates a non-nullable unique relationship with initial value', () => { - const { db, entity } = testFactory({ - country: { - code: primaryKey(String), - capital: oneOf('city', { unique: true }), - }, - city: { - name: primaryKey(String), - }, - }) - - const london = db.city.create({ name: 'London' }) - const country = db.country.create({ - code: 'uk', - capital: london, - }) - - const expectedCountry = entity('country', { - code: 'uk', - capital: london, - }) - - expect(country).toHaveRelationalProperty('capital', london) - - expect(country).toEqual(expectedCountry) - expect(db.country.findFirst({ where: { code: { equals: 'uk' } } })).toEqual( - expectedCountry, - ) - expect( - db.country.findFirst({ - where: { capital: { name: { equals: 'London' } } }, - }), - ).toEqual(expectedCountry) -}) - -it('creates a non-nullable unique relationship without initial value', () => { - const { db, entity } = testFactory({ - country: { - code: primaryKey(String), - capital: oneOf('city', { unique: true }), - }, - city: { - name: primaryKey(String), - }, - }) - - const country = db.country.create({ - code: 'uk', - }) - - const expectedCountry = entity('country', { - code: 'uk', - capital: undefined, - }) - - expect(country).toEqual(expectedCountry) - expect(db.country.findFirst({ where: { code: { equals: 'uk' } } })).toEqual( - expectedCountry, - ) -}) - -it('forbids creating a unique relationship to already referenced entity', () => { - const { db } = testFactory({ - country: { - code: primaryKey(String), - capital: oneOf('city', { unique: true }), - }, - city: { - name: primaryKey(String), - }, - }) - - const london = db.city.create({ name: 'London' }) - db.country.create({ - code: 'uk', - capital: london, - }) - - expect(() => - db.country.create({ - code: 'de', - capital: london, - }), - ).toThrow( - 'Failed to resolve a "ONE_OF" relationship to "city" at "country.capital" (code: "de"): the referenced "city" (name: "London") belongs to another "country" (code: "uk").', - ) - expect(db.country.findFirst({ where: { code: { equals: 'de' } } })).toEqual( - null, - ) -}) diff --git a/test/relations/one-to-one.operations.test.ts b/test/relations/one-to-one.operations.test.ts deleted file mode 100644 index a50fb009..00000000 --- a/test/relations/one-to-one.operations.test.ts +++ /dev/null @@ -1,336 +0,0 @@ -import { nullable, oneOf, primaryKey } from '../../src' -import { testFactory } from '../testUtils' - -/** - * Non-nullable one-to-one relationship. - */ -it('supports querying through a non-nullable relationship with initial value', () => { - const { db, entity } = testFactory({ - country: { - code: primaryKey(String), - capital: oneOf('city'), - }, - city: { - name: primaryKey(String), - }, - }) - - db.country.create({ - code: 'uk', - capital: db.city.create({ - name: 'London', - }), - }) - const expectedCountry = entity('country', { - code: 'uk', - capital: entity('city', { - name: 'London', - }), - }) - - expect( - db.country.findFirst({ - where: { - capital: { name: { equals: 'London' } }, - }, - }), - ).toEqual(expectedCountry) - expect( - db.country.findMany({ - where: { - capital: { name: { equals: 'London' } }, - }, - }), - ).toEqual([expectedCountry]) - - // Non-matching query yields no results. - expect( - db.country.findFirst({ - where: { - capital: { name: { equals: 'New Hampshire' } }, - }, - }), - ).toEqual(null) -}) - -it('supports querying through a non-nullable relationship without initial value', () => { - const { db } = testFactory({ - country: { - code: primaryKey(String), - capital: oneOf('city'), - }, - city: { - name: primaryKey(String), - }, - }) - - db.country.create({ - code: 'uk', - }) - - // Querying through the relationship is permitted - // but since it hasn't been set, no queries will match. - expect( - db.country.findFirst({ - where: { - capital: { name: { equals: 'London' } }, - }, - }), - ).toEqual(null) -}) - -it('supports querying through a deeply nested non-nullable relationship', () => { - const { db, entity } = testFactory({ - user: { - id: primaryKey(String), - address: { - billing: { - country: oneOf('country'), - }, - }, - }, - country: { - code: primaryKey(String), - }, - }) - - db.user.create({ - id: 'user-1', - address: { - billing: { - country: db.country.create({ - code: 'uk', - }), - }, - }, - }) - - expect( - db.user.findFirst({ - where: { - address: { - billing: { - country: { - code: { equals: 'uk' }, - }, - }, - }, - }, - }), - ).toEqual( - entity('user', { - id: 'user-1', - address: { - billing: { - country: entity('country', { - code: 'uk', - }), - }, - }, - }), - ) -}) - -it('supports querying through nested non-nullable relationships', () => { - const { db, entity } = testFactory({ - user: { - id: primaryKey(String), - location: oneOf('country'), - }, - country: { - code: primaryKey(String), - capital: oneOf('city'), - }, - city: { - name: primaryKey(String), - }, - }) - - db.user.create({ - id: 'user-1', - location: db.country.create({ - code: 'uk', - capital: db.city.create({ - name: 'London', - }), - }), - }) - - expect( - db.user.findFirst({ - where: { - location: { - capital: { - name: { equals: 'London' }, - }, - }, - }, - }), - ).toEqual( - entity('user', { - id: 'user-1', - location: entity('country', { - code: 'uk', - capital: entity('city', { - name: 'London', - }), - }), - }), - ) -}) - -/** - * Nullable one-to-one relationship. - */ -it('supports querying through a nullable relationship with initial value', () => { - const { db, entity } = testFactory({ - country: { - code: primaryKey(String), - capital: nullable(oneOf('city')), - }, - city: { - name: primaryKey(String), - }, - }) - - db.country.create({ - code: 'uk', - capital: db.city.create({ - name: 'London', - }), - }) - const expectedCountry = entity('country', { - code: 'uk', - capital: entity('city', { - name: 'London', - }), - }) - - expect( - db.country.findFirst({ - where: { - capital: { name: { equals: 'London' } }, - }, - }), - ).toEqual(expectedCountry) - expect( - db.country.findMany({ - where: { - capital: { name: { equals: 'London' } }, - }, - }), - ).toEqual([expectedCountry]) - - expect( - db.country.findFirst({ - where: { - capital: { name: { equals: 'New Hampshire' } }, - }, - }), - ).toEqual(null) -}) - -it('supports querying through a nullable relationship with null as initial value', () => { - const { db, entity } = testFactory({ - country: { - code: primaryKey(String), - capital: nullable(oneOf('city')), - }, - city: { - name: primaryKey(String), - }, - }) - - db.country.create({ - code: 'uk', - capital: null, - }) - - // Querying through the relationship is permitted - // but since it hasn't been set, no queries will match. - expect( - db.country.findFirst({ - where: { - capital: { name: { equals: 'London' } }, - }, - }), - ).toEqual(null) -}) - -it('supports querying through a nullable relationship without initial value', () => { - const { db } = testFactory({ - country: { - code: primaryKey(String), - capital: nullable(oneOf('city')), - }, - city: { - name: primaryKey(String), - }, - }) - - db.country.create({ - code: 'uk', - }) - - // Querying through the relationship is permitted - // but since it hasn't been set, no queries will match. - expect( - db.country.findFirst({ - where: { - capital: { name: { equals: 'London' } }, - }, - }), - ).toEqual(null) -}) - -it('supports querying through a deeply nested nullable relationship', () => { - const { db, entity } = testFactory({ - user: { - id: primaryKey(String), - address: { - billing: { - country: nullable(oneOf('country')), - }, - }, - }, - country: { - code: primaryKey(String), - }, - }) - - db.user.create({ - id: 'user-1', - address: { - billing: { - country: db.country.create({ - code: 'uk', - }), - }, - }, - }) - - expect( - db.user.findFirst({ - where: { - address: { - billing: { - country: { - code: { equals: 'uk' }, - }, - }, - }, - }, - }), - ).toEqual( - entity('user', { - id: 'user-1', - address: { - billing: { - country: entity('country', { - code: 'uk', - }), - }, - }, - }), - ) -}) diff --git a/test/relations/one-to-one.update.test.ts b/test/relations/one-to-one.update.test.ts deleted file mode 100644 index 2e90a2e8..00000000 --- a/test/relations/one-to-one.update.test.ts +++ /dev/null @@ -1,576 +0,0 @@ -import { primaryKey, oneOf, nullable } from '../../src' -import { ENTITY_TYPE, PRIMARY_KEY } from '../../src/glossary' -import { testFactory } from '../testUtils' - -/** - * Nullable one-to-one relationship. - */ -it('updates a nullable relationship with initial value to null', () => { - const { db, entity } = testFactory({ - country: { - code: primaryKey(String), - capital: nullable(oneOf('city')), - }, - city: { - name: primaryKey(String), - }, - }) - - db.country.create({ - code: 'uk', - capital: db.city.create({ - name: 'London', - }), - }) - - const nextCountry = db.country.update({ - where: { code: { equals: 'uk' } }, - data: { capital: null }, - }) - - expect(nextCountry).toHaveRelationalProperty('capital', null) - - expect(nextCountry?.capital).toEqual(null) - expect(db.country.findFirst({ where: { code: { equals: 'uk' } } })).toEqual( - entity('country', { - code: 'uk', - capital: null, - }), - ) - - // Un-referenced city still exists. - expect(db.city.findFirst({ where: { name: { equals: 'London' } } })).toEqual( - entity('city', { name: 'London' }), - ) -}) - -it('updates a nullable relationship without initial value to null', () => { - const { db, entity } = testFactory({ - country: { - code: primaryKey(String), - capital: nullable(oneOf('city')), - }, - city: { - name: primaryKey(String), - }, - }) - - db.country.create({ code: 'uk' }) - - const nextCountry = db.country.update({ - where: { code: { equals: 'uk' } }, - data: { capital: null }, - }) - - expect(nextCountry).toHaveRelationalProperty('capital', null) - - expect(nextCountry?.capital).toEqual(null) - expect(db.country.findFirst({ where: { code: { equals: 'uk' } } })).toEqual( - entity('country', { - code: 'uk', - capital: null, - }), - ) - - expect(db.city.count()).toEqual(0) -}) - -it('updates a nullable relationship with initial value to a new entity', () => { - const { db, entity } = testFactory({ - country: { - code: primaryKey(String), - capital: nullable(oneOf('city')), - }, - city: { - name: primaryKey(String), - }, - }) - - db.country.create({ - code: 'uk', - // Previously, "London" was the capital. - capital: db.city.create({ name: 'London' }), - }) - - const nextCapital = db.city.create({ name: 'Leads' }) - const nextCountry = db.country.update({ - where: { code: { equals: 'uk' } }, - data: { - // Update the capital to be a newly created "Leads". - capital: nextCapital, - }, - }) - - const expectedCountry = entity('country', { - code: 'uk', - capital: nextCapital, - }) - - expect(nextCountry).toHaveRelationalProperty('capital', nextCapital) - - // The updated country contains the updated relationship. - expect(nextCountry).toEqual(expectedCountry) - expect(db.country.findFirst({ where: { code: { equals: 'uk' } } })).toEqual( - expectedCountry, - ) - - // The country can be queried by the new relationship. - expect( - db.country.findFirst({ where: { capital: { name: { equals: 'Leads' } } } }), - ).toEqual(expectedCountry) - - // Newly created city exists. - expect(db.city.findFirst({ where: { name: { equals: 'Leads' } } })).toEqual( - nextCapital, - ) - - // Previously referenced city is not deleted. - expect(db.city.findFirst({ where: { name: { equals: 'London' } } })).toEqual( - entity('city', { name: 'London' }), - ) -}) - -it('updates a nullable relationship without initial value to a new entity', () => { - const { db, entity } = testFactory({ - country: { - code: primaryKey(String), - capital: nullable(oneOf('city')), - }, - city: { - name: primaryKey(String), - }, - }) - - db.country.create({ code: 'uk' }) - - const nextCapital = db.city.create({ name: 'Leads' }) - const nextCountry = db.country.update({ - where: { code: { equals: 'uk' } }, - data: { capital: nextCapital }, - }) - - const expectedCountry = entity('country', { - code: 'uk', - capital: nextCapital, - }) - - expect(nextCountry).toHaveRelationalProperty('capital', nextCapital) - - // The updated country contains the updated relationship. - expect(nextCountry).toEqual(expectedCountry) - expect(db.country.findFirst({ where: { code: { equals: 'uk' } } })).toEqual( - expectedCountry, - ) - - // The country can be queried by the new relationship. - expect( - db.country.findFirst({ where: { capital: { name: { equals: 'Leads' } } } }), - ).toEqual(expectedCountry) - - // Newly created city exists. - expect(db.city.findFirst({ where: { name: { equals: 'Leads' } } })).toEqual( - nextCapital, - ) -}) - -it('updates a deeply nested nullable relationship', () => { - const { db, entity } = testFactory({ - user: { - id: primaryKey(String), - address: { - billing: { - country: oneOf('country'), - }, - }, - }, - country: { - code: primaryKey(String), - }, - }) - - const user = db.user.create({ - id: 'user-1', - address: { - billing: { - country: db.country.create({ code: 'uk' }), - }, - }, - }) - - const nextUser = db.user.update({ - where: { id: { equals: 'user-1' } }, - data: { - address: { - billing: { - country: db.country.create({ code: 'us' }), - }, - }, - }, - strict: true, - }) - - const expectedUser = entity('user', { - id: 'user-1', - address: { - billing: { - country: entity('country', { - code: 'us', - }), - }, - }, - }) - - expect(nextUser).toEqual(expectedUser) -}) - -it('forbids updating a nullable relationship without initial value to a different model', () => { - const { db } = testFactory({ - country: { - code: primaryKey(String), - capital: nullable(oneOf('city')), - }, - city: { - name: primaryKey(String), - }, - user: { - id: primaryKey(String), - }, - }) - - const country = db.country.create({ code: 'uk' }) - - expect(() => - db.country.update({ - where: { code: { equals: 'uk' } }, - data: { - // @ts-expect-error Runtime value incompatibility. - capital: db.user.create({ id: 'user-1' }), - }, - }), - ).toThrow( - 'Failed to update a "ONE_OF" relationship to "city" at "country.capital" (code: "uk"): expected the next value to reference a "city" but got "user" (id: "user-1").', - ) - expect(country).toHaveRelationalProperty('capital', null) -}) - -it('forbids updating a nullable relationship with initial value to a different model', () => { - const { db } = testFactory({ - country: { - code: primaryKey(String), - capital: nullable(oneOf('city')), - }, - city: { - name: primaryKey(String), - }, - user: { - id: primaryKey(String), - }, - }) - - const london = db.city.create({ name: 'London' }) - const country = db.country.create({ - code: 'uk', - capital: london, - }) - - expect(() => - db.country.update({ - where: { code: { equals: 'uk' } }, - data: { - // @ts-expect-error Runtime value incompatibility. - capital: db.user.create({ id: 'user-1' }), - }, - }), - ).toThrow( - 'Failed to update a "ONE_OF" relationship to "city" at "country.capital" (code: "uk"): expected the next value to reference a "city" but got "user" (id: "user-1").', - ) - expect(country).toHaveRelationalProperty('capital', london) -}) - -/** - * Non-nullable one-to-one relationship. - */ -it('updates a non-nullable relationship with initial value to a new entity', () => { - const { db, entity } = testFactory({ - country: { - code: primaryKey(String), - capital: oneOf('city'), - }, - city: { - name: primaryKey(String), - }, - }) - - db.country.create({ - code: 'uk', - capital: db.city.create({ name: 'London' }), - }) - - const nextCapital = db.city.create({ name: 'Leads' }) - const nextCountry = db.country.update({ - where: { code: { equals: 'uk' } }, - data: { capital: nextCapital }, - }) - - const expectedCountry = entity('country', { - code: 'uk', - capital: nextCapital, - }) - - expect(nextCountry).toHaveRelationalProperty('capital', nextCapital) - - expect(nextCountry).toEqual(expectedCountry) - expect(db.country.findFirst({ where: { code: { equals: 'uk' } } })).toEqual( - expectedCountry, - ) - expect( - db.country.findFirst({ where: { capital: { name: { equals: 'Leads' } } } }), - ).toEqual(expectedCountry) - - // Newly referenced entity is created. - expect(db.city.findFirst({ where: { name: { equals: 'Leads' } } })).toEqual( - nextCapital, - ) - - // Un-referenced entity is not removed. - expect(db.city.findFirst({ where: { name: { equals: 'London' } } })).toEqual( - entity('city', { name: 'London' }), - ) -}) - -it('updates a non-nullable relationship without initial value to a new entity', () => { - const { db, entity } = testFactory({ - country: { - code: primaryKey(String), - capital: oneOf('city'), - }, - city: { - name: primaryKey(String), - }, - }) - - db.country.create({ code: 'uk' }) - - const nextCapital = db.city.create({ name: 'Leads' }) - const nextCountry = db.country.update({ - where: { code: { equals: 'uk' } }, - data: { capital: nextCapital }, - }) - - const expectedCountry = entity('country', { - code: 'uk', - capital: nextCapital, - }) - - expect(nextCountry).toHaveRelationalProperty('capital', nextCapital) - - expect(nextCountry).toEqual(expectedCountry) - expect(db.country.findFirst({ where: { code: { equals: 'uk' } } })).toEqual( - expectedCountry, - ) - expect( - db.country.findFirst({ where: { capital: { name: { equals: 'Leads' } } } }), - ).toEqual(expectedCountry) - - // Newly referenced entity is created. - expect(db.city.findFirst({ where: { name: { equals: 'Leads' } } })).toEqual( - nextCapital, - ) -}) - -it('preserves the relational property after arbitrary parent entity update', () => { - const { db } = testFactory({ - country: { - code: primaryKey(String), - name: String, - capital: oneOf('city'), - }, - city: { - name: primaryKey(String), - }, - }) - - const london = db.city.create({ - name: 'London', - }) - db.country.create({ - code: 'uk', - name: 'United Kingdom', - capital: london, - }) - - const nextCountry = db.country.update({ - where: { code: { equals: 'uk' } }, - data: { - name: 'The United Kingdom', - }, - }) - - expect(nextCountry).toHaveRelationalProperty('capital', london) - expect( - db.country.findFirst({ where: { code: { equals: 'uk' } } }), - ).toHaveRelationalProperty('capital', london) -}) - -it('forbids updating a non-nullable relationship without initial value to null', () => { - const { db } = testFactory({ - country: { - code: primaryKey(String), - capital: oneOf('city'), - }, - city: { - name: primaryKey(String), - }, - }) - - const country = db.country.create({ code: 'uk' }) - - expect(() => - db.country.update({ - where: { code: { equals: 'uk' } }, - data: { - // @ts-expect-error Runtime value incompatibility. - capital: null, - }, - }), - ).toThrow( - 'Failed to update a "ONE_OF" relationship to "city" at "country.capital" (code: "uk"): cannot update a non-nullable relationship to null.', - ) - // Non-nullable relationships are not instantiated without a value. - expect(country).not.toHaveProperty('capital') -}) - -it('forbids updating a non-nullable relationship without initial value to a different model', () => { - const { db } = testFactory({ - country: { - code: primaryKey(String), - capital: oneOf('city'), - }, - city: { - name: primaryKey(String), - }, - user: { - id: primaryKey(String), - }, - }) - - const country = db.country.create({ code: 'uk' }) - - expect(() => - db.country.update({ - where: { code: { equals: 'uk' } }, - data: { - // @ts-expect-error Runtime value incompatibility. - capital: db.user.create({ id: 'user-1' }), - }, - }), - ).toThrow( - 'Failed to update a "ONE_OF" relationship to "city" at "country.capital" (code: "uk"): expected the next value to reference a "city" but got "user" (id: "user-1").', - ) - expect(country).not.toHaveProperty('capital') - expect(db.user.getAll()).toEqual([ - { - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'user-1', - }, - ]) -}) - -it('forbids updating a unique non-nullable relationship to already referenced entity', () => { - const { db } = testFactory({ - country: { - code: primaryKey(String), - capital: oneOf('city', { unique: true }), - }, - city: { - name: primaryKey(String), - }, - }) - - const prevCapital = db.city.create({ name: 'London' }) - const country = db.country.create({ - code: 'uk', - capital: prevCapital, - }) - - const nextCapital = db.city.create({ name: 'Berlin' }) - db.country.create({ - code: 'de', - capital: nextCapital, - }) - - expect(() => - db.country.update({ - where: { code: { equals: 'uk' } }, - data: { capital: nextCapital }, - }), - ).toThrow( - 'Failed to resolve a "ONE_OF" relationship to "city" at "country.capital" (code: "uk"): the referenced "city" (name: "Berlin") belongs to another "country" (code: "de").', - ) - expect(country).toHaveRelationalProperty('capital', prevCapital) -}) - -it('forbids updating a relationship to a compatible plain object', () => { - const { db } = testFactory({ - country: { - code: primaryKey(String), - capital: oneOf('city'), - }, - city: { - name: primaryKey(String), - }, - }) - - db.country.create({ - code: 'uk', - capital: db.city.create({ - name: 'London', - }), - }) - - expect(() => - db.country.update({ - where: { code: { equals: 'uk' } }, - data: { - capital: { - name: 'New Hampshire', - }, - }, - }), - ).toThrow( - 'Failed to update a "ONE_OF" relationship to "city" at "country.capital" (code: "uk"): expected the next value to be an entity but got {"name":"New Hampshire"}.', - ) -}) - -it('forbids updating a relationship to a non-existing entity', () => { - const { db, entity } = testFactory({ - country: { - code: primaryKey(String), - capital: oneOf('city'), - }, - city: { - name: primaryKey(String), - }, - }) - - db.country.create({ - code: 'uk', - capital: db.city.create({ - name: 'London', - }), - }) - - expect(() => - db.country.update({ - where: { code: { equals: 'uk' } }, - data: { - capital: entity('city', { - name: 'New Hampshire', - }), - }, - }), - ).toThrow( - 'Failed to resolve a "ONE_OF" relationship to "city" at "country.capital" (code: "uk"): referenced entity "city" (name: "New Hampshire") does not exist.', - ) -}) diff --git a/test/testUtils.ts b/test/testUtils.ts deleted file mode 100644 index cfd7df63..00000000 --- a/test/testUtils.ts +++ /dev/null @@ -1,70 +0,0 @@ -import { performance, PerformanceObserver, PerformanceEntry } from 'perf_hooks' -import { factory } from '../src' -import { - ModelDictionary, - ENTITY_TYPE, - PRIMARY_KEY, - DATABASE_INSTANCE, - Value, -} from '../src/glossary' - -export function repeat(action: () => void, times: number) { - for (let i = 0; i < times; i++) { - action() - } -} - -export async function measurePerformance( - name: string, - fn: () => void | Promise, -): Promise { - const startEvent = `${name}Start` - const endEvent = `${name}End` - - return new Promise(async (resolve) => { - const observer = new PerformanceObserver((list) => { - const entries = list.getEntriesByName(name) - const lastEntry = entries[entries.length - 1] - - observer.disconnect() - resolve(lastEntry) - }) - observer.observe({ entryTypes: ['measure'] }) - - performance.mark(startEvent) - await fn() - performance.mark(endEvent) - performance.measure(name, startEvent, endEvent) - }) -} - -export function getThrownError(fn: () => void) { - try { - fn() - } catch (error) { - return error - } -} - -export function testFactory( - dictionary: Dictionary, -) { - const db = factory(dictionary) - - return { - db, - databaseInstance: db[DATABASE_INSTANCE], - dictionary, - entity( - modelName: ModelName, - properties: Value, - ) { - const entity = db[modelName].getAll()[0] - return { - [ENTITY_TYPE]: entity[ENTITY_TYPE], - [PRIMARY_KEY]: entity[PRIMARY_KEY], - ...properties, - } - }, - } -} diff --git a/test/tsconfig.json b/test/tsconfig.json deleted file mode 100644 index 7f00c041..00000000 --- a/test/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "declaration": false, - "noEmit": true, - "baseUrl": "../", - }, - "include": ["jest.d.ts", "**/*.ts"] -} diff --git a/test/tsconfig.test-d.json b/test/tsconfig.test-d.json deleted file mode 100644 index 289998e5..00000000 --- a/test/tsconfig.test-d.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "strict": true, - "noEmit": true, - "esModuleInterop": true, - "downlevelIteration": true - }, - "include": ["**/*.test-d.ts"] -} diff --git a/test/typings/DeepRequiredExactlyOne.test-d.ts b/test/typings/DeepRequiredExactlyOne.test-d.ts deleted file mode 100644 index 9f2413af..00000000 --- a/test/typings/DeepRequiredExactlyOne.test-d.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { DeepRequiredExactlyOne } from '../../src/glossary' - -type Shallow = DeepRequiredExactlyOne<{ a: number; b: string }> - -let shallow: Shallow = { a: 1 } -shallow = { b: '' } - -// @ts-expect-error Only one known property is allowed. -shallow = { a: 1, b: '' } - -type Nested = DeepRequiredExactlyOne<{ a: number; b: { c: { d: string } } }> - -let nested: Nested = { a: 1 } -nested = { b: { c: { d: '' } } } - -// @ts-expect-error Only one known property is allowed. -nested = { a: 1, b: { c: { d: '' } } } diff --git a/test/typings/nested-objects.test-d.ts b/test/typings/nested-objects.test-d.ts deleted file mode 100644 index 5c35a702..00000000 --- a/test/typings/nested-objects.test-d.ts +++ /dev/null @@ -1,254 +0,0 @@ -import { factory, primaryKey } from '@mswjs/data' - -const db = factory({ - user: { - id: primaryKey(String), - name: () => 'John', - address: { - billing: { - street: String, - }, - shipping: { - street: String, - }, - }, - }, -}) - -/** - * Create. - */ -db.user.create({ - address: { - billing: { - // Providing a known nested property. - street: 'Baker', - }, - }, -}) - -db.user.create({ - address: { - billing: { - // @ts-expect-error Property "foo" doesn't exist on "user.address.billing". - foo: 'Unknown', - }, - }, -}) - -db.user.create({ - address: { - // @ts-expect-error Property "unknown" doesn't exist on "user.address". - unknown: {}, - }, -}) - -/** - * Find first. - */ -db.user.findFirst({ - where: { - address: { - billing: { - street: { - equals: 'Baker', - }, - }, - }, - }, -}) - -db.user.findFirst({ - where: { - address: { - // @ts-expect-error Property "unknown" doesn't exist on "user.address". - unknown: {}, - }, - }, -}) - -db.user.findFirst({ - where: { - address: { - billing: { - // @ts-expect-error Property "unknown" doesn't exist on "user.address.billing". - unknown: { - equals: 'Baker', - }, - }, - }, - }, -}) - -/** - * Find many. - */ -db.user.findMany({ - where: { - address: { - billing: { - street: { - equals: 'Baker', - }, - }, - }, - }, -}) - -db.user.findMany({ - where: { - address: { - // @ts-expect-error Property "unknown" doesn't exist on "user.address". - unknown: {}, - }, - }, -}) - -db.user.findMany({ - where: { - address: { - billing: { - // @ts-expect-error Property "unknown" doesn't exist on "user.address.billing". - unknown: { - equals: 'Baker', - }, - }, - }, - }, -}) - -/** - * Update. - */ -db.user.update({ - where: { - id: { equals: 'abc-123' }, - }, - data: { - address: { - billing: { - // Updating a known nested property. - street: 'Sunwell Ave.', - }, - }, - }, -}) - -db.user.update({ - where: { - id: { equals: 'abc-123' }, - }, - data: { - id(value) { - return value.toUpperCase() - }, - address: { - billing: { - street(value) { - return value.toUpperCase() - }, - }, - }, - }, -}) - -db.user.update({ - where: { - id: { equals: 'abc-123' }, - }, - data: { - address: { - billing: { - // @ts-expect-error Property "foo" doesn't exist on "user.address.billing" - foo: 'Unknown', - }, - }, - }, -}) - -/** - * Update many. - */ -db.user.updateMany({ - where: { - address: { - billing: { - street: { - equals: 'Baker', - }, - }, - }, - }, - data: { - address: { - billing: { - street(value) { - return value.toUpperCase() - }, - }, - }, - }, -}) - -/** - * Sorting. - */ -db.user.findMany({ - where: {}, - orderBy: { - address: { - billing: { - street: 'asc', - }, - }, - }, -}) - -db.user.findMany({ - where: {}, - // @ts-expect-error Must use "asc"/"desc" as sort direction. - orderBy: { - address: { - billing: { - street: 'UNKNOWN VALUE', - }, - }, - }, -}) - -db.user.findMany({ - where: {}, - // @ts-expect-error Must sort by a single criteria - // using object as the "orderBy" value. - orderBy: { - address: { - billing: { - street: 'asc', - }, - shipping: { - street: 'desc', - }, - }, - }, -}) - -// Multi-criteria sorting. -db.user.findMany({ - where: {}, - orderBy: [ - { - address: { - billing: { - street: 'asc', - }, - }, - }, - { - address: { - shipping: { - street: 'desc', - }, - }, - }, - ], -}) diff --git a/test/typings/relations.test-d.ts b/test/typings/relations.test-d.ts deleted file mode 100644 index 55d397d6..00000000 --- a/test/typings/relations.test-d.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { factory, oneOf, primaryKey } from '@mswjs/data' - -const db = factory({ - user: { - id: primaryKey(String), - country: oneOf('country'), - stats: { - revision: oneOf('revision'), - }, - }, - country: { - code: primaryKey(String), - }, - revision: { - id: primaryKey(String), - updatedAt: Number, - }, -}) - -const user = db.user.create() -user.country?.code.toUpperCase() - -// @ts-expect-error Unknown property "foo" on "country". -user.country.foo - -user.stats.revision?.id -user.stats.revision?.updatedAt.toFixed() - -// @ts-expect-error Unknown property "foo" on "revision". -user.stats.revision?.foo diff --git a/test/typings/strict-queries.test-d.ts b/test/typings/strict-queries.test-d.ts deleted file mode 100644 index 1719e7c5..00000000 --- a/test/typings/strict-queries.test-d.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { factory, primaryKey } from '@mswjs/data' - -const db = factory({ - user: { - id: primaryKey(String), - }, -}) - -// @ts-expect-error Value it potentially "null". -db.user.findFirst({ - where: { id: { equals: 'user-1' } }, -}).id - -// Using "strict" the value is never null. -db.user.findFirst({ - where: { id: { equals: 'user-1' } }, - strict: true, -}).id - -// @ts-expect-error Value it potentially "null". -db.user.update({ - where: { id: { equals: 'user-1' } }, -}).id - -// Using "strict" the value is never null. -db.user.update({ - where: { id: { equals: 'user-1' } }, - data: {}, - strict: true, -}).id - -// @ts-expect-error Value it potentially "null". -db.user.updateMany({ - where: { id: { equals: 'user-1' } }, -}).forEach - -// Using "strict" the value is never null. -db.user.updateMany({ - where: { id: { equals: 'user-1' } }, - data: {}, - strict: true, -}).forEach - -// @ts-expect-error Value it potentially "null". -db.user.delete({ - where: { id: { equals: 'user-1' } }, -}).id - -// Using "strict" the value is never null. -db.user.delete({ - where: { id: { equals: 'user-1' } }, - strict: true, -}).id - -// @ts-expect-error Value it potentially "null". -db.user.deleteMany({ - where: { id: { equals: 'user-1' } }, -}).forEach - -// Using "strict" the value is never null. -db.user.deleteMany({ - where: { id: { equals: 'user-1' } }, - strict: true, -}).forEach diff --git a/test/utils/capitalize.test.ts b/test/utils/capitalize.test.ts deleted file mode 100644 index 601e3a5f..00000000 --- a/test/utils/capitalize.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { capitalize } from '../../src/utils/capitalize' - -it('capitalizes a given string', () => { - expect(capitalize('user')).toEqual('User') - expect(capitalize('deliveryType')).toEqual('DeliveryType') - expect(capitalize('AlreadyCapitalized')).toEqual('AlreadyCapitalized') -}) diff --git a/test/utils/definePropertyAtPath.test.ts b/test/utils/definePropertyAtPath.test.ts deleted file mode 100644 index 913c7752..00000000 --- a/test/utils/definePropertyAtPath.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { definePropertyAtPath } from '../../src/utils/definePropertyAtPath' - -type AnyObject = Record - -describe('definePropertyAtPath()', () => { - it('defines a root property by give name', () => { - const target: AnyObject = {} - definePropertyAtPath(target, ['a'], { - get() { - return 'hello world' - }, - }) - expect(target.a).toEqual('hello world') - }) - - it('defines a nested property at a given path', () => { - const target: AnyObject = {} - definePropertyAtPath(target, ['a', 'b', 'c'], { - get() { - return 'hello world' - }, - }) - expect(target.a.b.c).toEqual('hello world') - }) - - it('defines properies with dots in them', () => { - const target: AnyObject = {} - definePropertyAtPath(target, ['a.b.c'], { - get() { - return 'hello world' - }, - }) - expect(target['a.b.c']).toEqual('hello world') - }) - - it('defines deep properies with dots in them', () => { - const target: AnyObject = {} - definePropertyAtPath(target, ['a.b.c', 'e.d.f'], { - get() { - return 'hello world' - }, - }) - expect(target['a.b.c']['e.d.f']).toEqual('hello world') - }) -}) diff --git a/test/utils/findPrimaryKey.test.ts b/test/utils/findPrimaryKey.test.ts deleted file mode 100644 index 073fa434..00000000 --- a/test/utils/findPrimaryKey.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { primaryKey } from '../../src' -import { findPrimaryKey } from '../../src/utils/findPrimaryKey' - -it('returns the primary key property name of the model definition', () => { - const result = findPrimaryKey({ - id: primaryKey(String), - }) - expect(result).toEqual('id') -}) - -it('returns undefined if the model definition contains property-compatible object', () => { - const result = findPrimaryKey({ - id: { - // This object is compatible with the "PrimaryKey" class - // but is not an instance of that class. - getValue() { - return 'abc-123' - }, - }, - }) - expect(result).toBeUndefined() -}) - -it('returns undefined if the model definition has no primary key', () => { - const result = findPrimaryKey({}) - expect(result).toBeUndefined() -}) diff --git a/test/utils/first.test.ts b/test/utils/first.test.ts deleted file mode 100644 index 41f357da..00000000 --- a/test/utils/first.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { first } from '../../src/utils/first' - -test('returns the first item of a one-item array', () => { - expect(first([10])).toBe(10) -}) - -test('returns the first item of a non-empty array', () => { - expect(first([1, 2, 3])).toBe(1) -}) - -test('returns null given an empty array', () => { - expect(first([])).toBeNull() -}) - -test('returns null given a falsy value', () => { - expect( - first( - // @ts-expect-error Runtime null value. - null, - ), - ).toBeNull() - expect( - first( - // @ts-expect-error Runtime undefined value. - undefined, - ), - ).toBeNull() -}) diff --git a/test/utils/generateGraphQLHandlers.test.ts b/test/utils/generateGraphQLHandlers.test.ts deleted file mode 100644 index e5697d74..00000000 --- a/test/utils/generateGraphQLHandlers.test.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { - GraphQLBoolean, - GraphQLFloat, - GraphQLID, - GraphQLInt, - GraphQLString, -} from 'graphql' -import { primaryKey } from '../../src' -import { - comparatorTypes, - getGraphQLType, - getQueryTypeByValueType, - definitionToFields, -} from '../../src/model/generateGraphQLHandlers' - -describe('getGraphQLType', () => { - it('derives GraphQL type from a variable', () => { - expect(getGraphQLType(String)).toEqual(GraphQLString) - expect(getGraphQLType(Number)).toEqual(GraphQLInt) - expect(getGraphQLType(Date)).toEqual(GraphQLString) - }) -}) - -describe('getQueryTypeByValueType', () => { - it('returns ID query type given GraphQLID value type', () => { - expect(getQueryTypeByValueType(GraphQLID)).toEqual( - comparatorTypes.IdQueryType, - ) - }) - - it('returns Int query type given GraphQLInt value type', () => { - expect(getQueryTypeByValueType(GraphQLInt)).toEqual( - comparatorTypes.IntQueryType, - ) - }) - - it('returns Boolean query type given GraphQLBoolean value type', () => { - expect(getQueryTypeByValueType(GraphQLBoolean)).toEqual( - comparatorTypes.BooleanQueryType, - ) - }) - - it('returns String query type given GraphQLString value type', () => { - expect(getQueryTypeByValueType(GraphQLString)).toEqual( - comparatorTypes.StringQueryType, - ) - }) - - it('returns String query type given an unknown GraphQLScalar type', () => { - expect(getQueryTypeByValueType(GraphQLFloat)).toEqual( - comparatorTypes.StringQueryType, - ) - }) -}) - -describe('definitionToFields', () => { - it('derives fields, input fields, and query input fields from a model definition', () => { - expect( - definitionToFields({ - id: primaryKey(String), - firstName: String, - age: Number, - }), - ).toEqual({ - fields: { - id: { type: GraphQLID }, - firstName: { type: GraphQLString }, - age: { type: GraphQLInt }, - }, - inputFields: { - id: { type: GraphQLID }, - firstName: { type: GraphQLString }, - age: { type: GraphQLInt }, - }, - queryInputFields: { - id: { type: comparatorTypes.IdQueryType }, - firstName: { type: comparatorTypes.StringQueryType }, - age: { type: comparatorTypes.IntQueryType }, - }, - }) - }) -}) diff --git a/test/utils/generateRestHandlers.test.ts b/test/utils/generateRestHandlers.test.ts deleted file mode 100644 index 2a23b4b4..00000000 --- a/test/utils/generateRestHandlers.test.ts +++ /dev/null @@ -1,209 +0,0 @@ -import { response, restContext } from 'msw' -import { primaryKey } from '../..' -import { ModelDefinition } from '../../src/glossary' -import { - OperationError, - OperationErrorType, -} from '../../src/errors/OperationError' -import { - createUrlBuilder, - getResponseStatusByErrorType, - withErrors, - parseQueryParams, -} from '../../src/model/generateRestHandlers' - -describe('createUrlBuilder', () => { - it('builds a relative URL given no base URL', () => { - const buildUrl = createUrlBuilder() - expect(buildUrl('/users')).toEqual('/users') - }) - - it('builds an absolute URL given a base URL', () => { - const buildUrl = createUrlBuilder('https://example.com') - expect(buildUrl('/users')).toEqual('https://example.com/users') - }) -}) - -describe('getResponseStatusByErrorType', () => { - it('returns 505 for the not-found operation error', () => { - const notFoundError = new OperationError(OperationErrorType.EntityNotFound) - expect(getResponseStatusByErrorType(notFoundError)).toEqual(404) - }) - - it('returns 409 for the duplicate key operation error', () => { - const duplicateKeyError = new OperationError( - OperationErrorType.DuplicatePrimaryKey, - ) - expect(getResponseStatusByErrorType(duplicateKeyError)).toEqual(409) - }) - - it('returns 500 for any other operation error', () => { - const unknownError = new OperationError('UNKNOWN') - expect( - getResponseStatusByErrorType( - // @ts-expect-error Runtime unknown error instance. - unknownError, - ), - ).toEqual(500) - }) -}) - -describe('withErrors', () => { - it('executes a successful handler as-is', async () => { - const handler = withErrors((req, res, ctx) => { - return res(ctx.text('ok')) - }) - const result = await handler( - // @ts-expect-error - {}, - response, - restContext, - ) - - expect(result).toHaveProperty('status', 200) - expect(result).toHaveProperty('body', 'ok') - }) - - it('handles a not-found error as a 404', async () => { - const handler = withErrors(() => { - throw new OperationError(OperationErrorType.EntityNotFound, 'Not found') - }) - const result = await handler( - // @ts-expect-error - {}, - response, - restContext, - ) - - expect(result).toHaveProperty('status', 404) - expect(result).toHaveProperty( - 'body', - JSON.stringify({ message: 'Not found' }), - ) - }) - - it('handles a duplicate key error as 409', async () => { - const handler = withErrors(() => { - throw new OperationError( - OperationErrorType.DuplicatePrimaryKey, - 'Duplicate key', - ) - }) - const result = await handler( - // @ts-expect-error - {}, - response, - restContext, - ) - - expect(result).toHaveProperty('status', 409) - expect(result).toHaveProperty( - 'body', - JSON.stringify({ message: 'Duplicate key' }), - ) - }) - - it('handles internal errors as a 500', async () => { - const handler = withErrors(() => { - throw new Error('Arbitrary error') - }) - const result = await handler( - // @ts-expect-error - {}, - response, - restContext, - ) - - expect(result).toHaveProperty('status', 500) - expect(result).toHaveProperty( - 'body', - JSON.stringify({ message: 'Arbitrary error' }), - ) - }) -}) - -describe('parseQueryParams', () => { - const definition: ModelDefinition = { - id: primaryKey(String), - firstName: String, - } - - it('parses search params into pagination and filters', () => { - const result = parseQueryParams( - 'user', - definition, - new URLSearchParams({ - take: '10', - skip: '5', - firstName: 'John', - }), - ) - expect(result).toEqual({ - take: 10, - skip: 5, - cursor: null, - filters: { - firstName: { equals: 'John' }, - }, - }) - }) - - it('returns null as the "take" when none is set', () => { - const result = parseQueryParams( - 'user', - definition, - new URLSearchParams({ - skip: '5', - }), - ) - expect(result).toHaveProperty('take', null) - }) - - it('returns null as the "skip" when none is set', () => { - const result = parseQueryParams( - 'user', - definition, - new URLSearchParams({ - take: '10', - }), - ) - expect(result).toHaveProperty('skip', null) - }) - - it('returns null as the "cursor" when none is set', () => { - const result = parseQueryParams( - 'user', - definition, - new URLSearchParams({ - take: '10', - skip: '5', - }), - ) - expect(result).toHaveProperty('cursor', null) - }) - - it('returns an empty object given no model definition-based params', () => { - const result = parseQueryParams( - 'user', - definition, - new URLSearchParams({ take: '10', skip: '5' }), - ) - expect(result).toHaveProperty('filters', {}) - }) - - it('throws an error given an unknown model definition-based param', () => { - const parse = () => { - return parseQueryParams( - 'user', - definition, - new URLSearchParams({ - unknownProp: 'yes', - }), - ) - } - - expect(parse).toThrow( - 'Failed to query the "user" model: unknown property "unknownProp".', - ) - }) -}) diff --git a/test/utils/identity.test.ts b/test/utils/identity.test.ts deleted file mode 100644 index b9cb16b7..00000000 --- a/test/utils/identity.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { identity } from '../../src/utils/identity' - -test('returns a function that returns a given value', () => { - const id = identity(5) - expect(id).toBeInstanceOf(Function) - expect(id()).toBe(5) -}) diff --git a/test/utils/inheritInternalProperties.test.ts b/test/utils/inheritInternalProperties.test.ts deleted file mode 100644 index 1f29afde..00000000 --- a/test/utils/inheritInternalProperties.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Entity, ENTITY_TYPE, PRIMARY_KEY } from '../../src/glossary' -import { inheritInternalProperties } from '../../src/utils/inheritInternalProperties' - -it('inherits internal properties from the given entity', () => { - const target = { - id: 'abc-123', - firstName: 'John', - } - const entity: Entity = { - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - } - - inheritInternalProperties(target, entity) - - expect(Object.keys(target)).toEqual(['id', 'firstName']) - expect(Object.getOwnPropertySymbols(target)).toEqual([ - ENTITY_TYPE, - PRIMARY_KEY, - ]) - expect(target).toEqual({ - [ENTITY_TYPE]: 'user', - [PRIMARY_KEY]: 'id', - id: 'abc-123', - firstName: 'John', - }) -}) - -it('throws an exception given a corrupted source entity', () => { - expect(() => - inheritInternalProperties( - { firstName: 'John' }, - // @ts-expect-error Intentionally corrupt entity. - { id: 'abc-123' }, - ), - ).toThrow( - 'Failed to inherit internal properties from ({"id":"abc-123"}) to ({"firstName":"John"}): provided source entity has no entity type specified.', - ) - - expect(() => - inheritInternalProperties( - { - firstName: 'John', - }, - // @ts-expect-error Intentionally corrupt entity. - { - [ENTITY_TYPE]: 'user', - id: 'abc-123', - }, - ), - ).toThrow( - 'Failed to inherit internal properties from ({"id":"abc-123"}) to ({"firstName":"John"}): provided source entity has no primary key specified.', - ) -}) diff --git a/test/utils/isModelValueType.test.ts b/test/utils/isModelValueType.test.ts deleted file mode 100644 index ee647b10..00000000 --- a/test/utils/isModelValueType.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { isModelValueType } from '../../src/utils/isModelValueType' - -it('returns true given a string', () => { - expect(isModelValueType('I am a string')).toBe(true) -}) - -it('returns true given a new string', () => { - expect(isModelValueType(String())).toBe(true) -}) - -it('returns true given a number', () => { - expect(isModelValueType(100)).toBe(true) -}) - -it('returns true given a new number', () => { - expect(isModelValueType(Number())).toBe(true) -}) - -it('returns true given a Date', () => { - expect(isModelValueType(new Date())).toBe(true) -}) - -it('returns true given a new array', () => { - expect(isModelValueType(new Array())).toBe(true) -}) - -it('returns true given an array with primitive values', () => { - expect(isModelValueType(['I am a string', 100])).toBe(true) -}) - -it('returns true when given an array with non-primitive values', () => { - expect(isModelValueType(['I am a string', {}])).toBe(true) -}) - -it('returns true when given nested primitive arrays', () => { - expect(isModelValueType(['I am a string', [100]])).toBe(true) -}) - -it('returns false given an undefined', () => { - expect(isModelValueType(undefined)).toBe(false) -}) - -it('returns false given a null', () => { - expect(isModelValueType(null)).toBe(false) -}) diff --git a/test/utils/isObject.test.ts b/test/utils/isObject.test.ts deleted file mode 100644 index 8fdcd8ce..00000000 --- a/test/utils/isObject.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { isObject } from '../../src/utils/isObject' - -it('returns true given an empty object', () => { - expect(isObject({})).toEqual(true) -}) - -it('returns true given an object with values', () => { - expect(isObject({ a: 1, b: ['foo'] })).toEqual(true) -}) - -it('returns false given falsy values', () => { - expect(isObject(undefined)).toEqual(false) - expect(isObject(null)).toEqual(false) - expect(isObject(false)).toEqual(false) -}) - -it('returns false given an array', () => { - expect(isObject([])).toEqual(false) - expect(isObject([{ a: 1 }])).toEqual(false) -}) diff --git a/test/utils/parseModelDefinition.test.ts b/test/utils/parseModelDefinition.test.ts deleted file mode 100644 index dd13b5cd..00000000 --- a/test/utils/parseModelDefinition.test.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { - ParsedModelDefinition, - parseModelDefinition, -} from '../../src/model/parseModelDefinition' -import { manyOf, oneOf, primaryKey } from '../../src' -import { ModelDictionary } from '../../src/glossary' -import { Relation, RelationKind } from '../../src/relations/Relation' - -it('parses a plain model definition', () => { - const dictionary = { - user: { - id: primaryKey(String), - firstName: String, - }, - } - const result = parseModelDefinition(dictionary, 'user', dictionary.user) - - expect(result).toEqual({ - primaryKey: 'id', - properties: [['id'], ['firstName']], - relations: [], - } as ParsedModelDefinition) -}) - -it('parses a model definition with relations', () => { - const dictionary = { - user: { - id: primaryKey(String), - firstName: String, - country: oneOf('country', { unique: true }), - posts: manyOf('post'), - }, - country: { - code: primaryKey(String), - }, - post: { - id: primaryKey(String), - }, - } - const result = parseModelDefinition(dictionary, 'user', dictionary['user']) - - expect(result).toEqual({ - primaryKey: 'id', - properties: [['id'], ['firstName']], - relations: [ - { - propertyPath: ['country'], - relation: new Relation({ - to: 'country', - kind: RelationKind.OneOf, - attributes: { - unique: true, - }, - }), - }, - { - propertyPath: ['posts'], - relation: new Relation({ - to: 'post', - kind: RelationKind.ManyOf, - }), - }, - ], - } as ParsedModelDefinition) -}) - -it('parses a model definition with nested objects', () => { - const dictionary: ModelDictionary = { - user: { - id: primaryKey(String), - address: { - billing: { - street: String, - houseNumber: String, - country: oneOf('country'), - }, - }, - activity: { - posts: manyOf('post', { unique: true }), - }, - }, - post: { - id: primaryKey(String), - }, - country: { - code: primaryKey(String), - }, - } - - const result = parseModelDefinition(dictionary, 'user', dictionary.user) - - expect(result).toEqual({ - primaryKey: 'id', - properties: [ - ['id'], - ['address', 'billing', 'street'], - ['address', 'billing', 'houseNumber'], - ], - relations: [ - { - propertyPath: ['address', 'billing', 'country'], - relation: new Relation({ - to: 'country', - kind: RelationKind.OneOf, - }), - }, - { - propertyPath: ['activity', 'posts'], - relation: new Relation({ - to: 'post', - kind: RelationKind.ManyOf, - attributes: { - unique: true, - }, - }), - }, - ], - } as ParsedModelDefinition) -}) - -it('throws an error when provided a model definition with multiple primary keys', () => { - const dictionary = { - user: { - id: primaryKey(String), - role: primaryKey(String), - }, - } - const parse = () => parseModelDefinition(dictionary, 'user', dictionary.user) - - expect(parse).toThrow( - 'Failed to parse a model definition for "user": cannot have both properties "id" and "role" as a primary key.', - ) -}) - -it('throws an error when provided a model definition without a primary key', () => { - const dictionary = { - user: { - firstName: String, - }, - } - const parse = () => parseModelDefinition(dictionary, 'user', dictionary.user) - - expect(parse).toThrow( - 'Failed to parse a model definition for "user": model is missing a primary key. Did you forget to mark one of its properties using the "primaryKey" function?', - ) -}) diff --git a/test/utils/spread.test.ts b/test/utils/spread.test.ts deleted file mode 100644 index e43ddad6..00000000 --- a/test/utils/spread.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { spread } from '../../src/utils/spread' - -it('spreads a plain object', () => { - const source = { a: 1, b: { c: 2 } } - const target = spread(source) - - expect(target).toEqual(source) - - source.a = 2 - source.b.c = 3 - - expect(target.a).toEqual(1) - expect(target.b.c).toEqual(2) -}) - -it('preserves property getters', () => { - const source = { a: 1, getCount: undefined } - Object.defineProperty(source, 'getCount', { - get() { - return 123 - }, - }) - - const target = spread(source) - - expect(target.a).toEqual(1) - expect(Object.getOwnPropertyDescriptor(target, 'getCount')).toEqual( - Object.getOwnPropertyDescriptor(source, 'getCount'), - ) - expect(target.getCount).toEqual(123) -}) - -it('does not preserve symbols', () => { - const symbol = Symbol('secret') - const source = {} as { [symbol]: number } - Object.defineProperty(source, symbol, { value: 123 }) - - const target = spread(source) - - expect(target[symbol]).toBeUndefined() - expect(Object.getOwnPropertySymbols(target)).toEqual([]) -}) diff --git a/test/utils/updateEntity.test.ts b/test/utils/updateEntity.test.ts deleted file mode 100644 index ab644212..00000000 --- a/test/utils/updateEntity.test.ts +++ /dev/null @@ -1,663 +0,0 @@ -import { primaryKey, oneOf, manyOf, nullable } from '../../src' -import { updateEntity } from '../../src/model/updateEntity' -import { testFactory } from '../../test/testUtils' - -describe('plain value', () => { - it('updates a single root-level property', () => { - const { db, dictionary, entity } = testFactory({ - user: { - id: primaryKey(String), - firstName: String, - }, - }) - - const user = db.user.create({ - id: 'user-1', - firstName: 'John', - }) - const nextUser = updateEntity(user, { firstName: 'Jack' }, dictionary.user) - - expect(nextUser).toEqual( - entity('user', { - id: 'user-1', - firstName: 'Jack', - }), - ) - }) - - it('updates multiple root-level properties', () => { - const { db, dictionary, entity } = testFactory({ - user: { - id: primaryKey(String), - firstName: String, - lastName: String, - }, - }) - - const user = db.user.create({ - id: 'user-1', - firstName: 'John', - lastName: 'Maverick', - }) - const nextUser = updateEntity( - user, - { - firstName: 'Kate', - lastName: 'Brook', - }, - dictionary.user, - ) - - expect(nextUser).toEqual( - entity('user', { - id: 'user-1', - firstName: 'Kate', - lastName: 'Brook', - }), - ) - }) - - it('updates a nested property', () => { - const { db, dictionary, entity } = testFactory({ - user: { - id: primaryKey(String), - address: { - billing: { - street: String, - }, - }, - }, - }) - - const user = db.user.create({ - id: 'user-1', - address: { - billing: { - street: 'Baker st.', - }, - }, - }) - const nextUser = updateEntity( - user, - { - address: { - billing: { - street: 'Sunwell ave.', - }, - }, - }, - dictionary.user, - ) - - expect(nextUser).toEqual( - entity('user', { - id: 'user-1', - address: { - billing: { - street: 'Sunwell ave.', - }, - }, - }), - ) - }) - - it('updates multiple nested properties', () => { - const { db, dictionary, entity } = testFactory({ - user: { - id: primaryKey(String), - address: { - billing: { - street: String, - }, - delivery: { - street: String, - }, - }, - }, - }) - - const user = db.user.create({ - id: 'user-1', - address: { - billing: { street: 'Baker st.' }, - delivery: { street: 'Brightingale' }, - }, - }) - const nextUser = updateEntity( - user, - { - address: { - billing: { street: 'Sunwell ave.' }, - delivery: { street: 'Theodor' }, - }, - }, - dictionary.user, - ) - - expect(nextUser).toEqual( - entity('user', { - id: 'user-1', - address: { - billing: { street: 'Sunwell ave.' }, - delivery: { street: 'Theodor' }, - }, - }), - ) - }) - - it('skips unknown model properties', () => { - const { db, dictionary, entity } = testFactory({ - user: { - id: primaryKey(String), - firstName: String, - }, - }) - - const user = db.user.create({ - id: 'user-1', - firstName: 'John', - }) - const nextUser = updateEntity( - user, - { - location: 'Madrid', - }, - dictionary.user, - ) - - expect(nextUser).toEqual( - entity('user', { - id: 'user-1', - firstName: 'John', - }), - ) - }) -}) - -describe('evolver function', () => { - it('updates a single root-level property', () => { - const { db, dictionary, entity } = testFactory({ - user: { - id: primaryKey(String), - firstName: String, - age: Number, - }, - }) - - const user = db.user.create({ - id: 'user-1', - firstName: 'John', - age: 24, - }) - - const firstNameEvolver = jest.fn(() => 'Jack') - const nextUser = updateEntity( - user, - { - firstName: firstNameEvolver, - }, - dictionary.user, - ) - - expect(firstNameEvolver).toHaveBeenCalledWith('John', user) - expect(nextUser).toEqual( - entity('user', { - id: 'user-1', - firstName: 'Jack', - age: 24, - }), - ) - }) - - it('updates a multiple root-level properties', () => { - const { db, dictionary, entity } = testFactory({ - user: { - id: primaryKey(String), - firstName: String, - age: Number, - }, - }) - - const user = db.user.create({ - id: 'user-1', - firstName: 'John', - age: 24, - }) - - const firstNameEvolver = jest.fn(() => 'Jack') - const ageEvolver = jest.fn(() => 31) - const nextUser = updateEntity( - user, - { - firstName: firstNameEvolver, - age: ageEvolver, - }, - dictionary.user, - ) - - expect(firstNameEvolver).toHaveBeenCalledWith('John', user) - expect(ageEvolver).toHaveBeenCalledWith(24, user) - expect(nextUser).toEqual( - entity('user', { - id: 'user-1', - firstName: 'Jack', - age: 31, - }), - ) - }) - - it('updates a nested property', () => { - const { db, dictionary, entity } = testFactory({ - user: { - id: primaryKey(String), - address: { - billing: { - street: String, - }, - }, - }, - }) - - const user = db.user.create({ - id: 'user-1', - address: { - billing: { street: 'Baker st.' }, - }, - }) - - const streetEvolver = jest.fn(() => 'Sunwell ave.') - const nextUser = updateEntity( - user, - { - address: { - billing: { street: streetEvolver }, - }, - }, - dictionary.user, - ) - - expect(streetEvolver).toHaveBeenCalledWith('Baker st.', user) - expect(nextUser).toEqual( - entity('user', { - id: 'user-1', - address: { - billing: { street: 'Sunwell ave.' }, - }, - }), - ) - }) - - it('updates mutliple nested properties', () => { - const { db, dictionary, entity } = testFactory({ - user: { - id: primaryKey(String), - address: { - billing: { - street: String, - }, - delivery: { - street: String, - }, - }, - }, - }) - - const user = db.user.create({ - id: 'user-1', - address: { - billing: { street: 'Baker st.' }, - delivery: { street: 'Brightingale' }, - }, - }) - - const billingStreetEvolver = jest.fn(() => 'Sunwell ave.') - const deliveryStreetEvolver = jest.fn(() => 'Theodor') - const nextUser = updateEntity( - user, - { - address: { - billing: { street: billingStreetEvolver }, - delivery: { street: deliveryStreetEvolver }, - }, - }, - dictionary.user, - ) - - expect(billingStreetEvolver).toHaveBeenCalledWith('Baker st.', user) - expect(deliveryStreetEvolver).toHaveBeenCalledWith('Brightingale', user) - expect(nextUser).toEqual( - entity('user', { - id: 'user-1', - address: { - billing: { street: 'Sunwell ave.' }, - delivery: { street: 'Theodor' }, - }, - }), - ) - }) -}) - -describe('relationship', () => { - it('updates a single root-level "ONE_OF" relationship', () => { - const { db, dictionary, entity } = testFactory({ - user: { - id: primaryKey(String), - country: oneOf('country'), - }, - country: { - code: primaryKey(String), - }, - }) - - const user = db.user.create({ - id: 'user-1', - country: db.country.create({ code: 'uk' }), - }) - const nextUser = updateEntity( - user, - { - country: db.country.create({ code: 'us' }), - }, - dictionary.user, - ) - - expect(nextUser).toHaveRelationalProperty( - 'country', - entity('country', { - code: 'us', - }), - ) - }) - - it('updates multiple root-level "ONE_OF" relationships', () => { - const { db, dictionary, entity } = testFactory({ - user: { - id: primaryKey(String), - role: oneOf('role'), - country: oneOf('country'), - }, - role: { - name: primaryKey(String), - }, - country: { - code: primaryKey(String), - }, - }) - - const user = db.user.create({ - id: 'user-1', - role: db.role.create({ name: 'reader' }), - country: db.country.create({ code: 'uk' }), - }) - const nextUser = updateEntity( - user, - { - role: db.role.create({ name: 'moderator' }), - country: db.country.create({ code: 'us' }), - }, - dictionary.user, - ) - - expect(nextUser).toHaveRelationalProperty( - 'role', - entity('role', { name: 'moderator' }), - ) - expect(nextUser).toHaveRelationalProperty( - 'country', - entity('country', { code: 'us' }), - ) - }) - - it('updates a nested "ONE_OF" relationship', () => { - const { db, dictionary, entity } = testFactory({ - user: { - id: primaryKey(String), - address: { - country: oneOf('country'), - }, - }, - country: { - code: primaryKey(String), - }, - }) - - const user = db.user.create({ - id: 'user-1', - address: { - country: db.country.create({ code: 'uk' }), - }, - }) - const nextUser = updateEntity( - user, - { - address: { - country: db.country.create({ code: 'us' }), - }, - }, - dictionary.user, - ) - - expect(nextUser).toEqual( - entity('user', { - id: 'user-1', - address: { - country: entity('country', { code: 'us' }), - }, - }), - ) - expect(nextUser.address).toHaveRelationalProperty( - 'country', - entity('country', { code: 'us' }), - ) - }) - - it('updates a root-level "MANY_OF" relationship', () => { - const { db, dictionary, entity } = testFactory({ - user: { - id: primaryKey(String), - posts: manyOf('post'), - }, - post: { - title: primaryKey(String), - }, - }) - - const user = db.user.create({ - id: 'user-1', - posts: [ - db.post.create({ title: 'First post' }), - db.post.create({ title: 'Second post' }), - ], - }) - - const nextUser = updateEntity( - user, - { - posts: [db.post.create({ title: 'Third post' })], - }, - dictionary.user, - ) - - expect(nextUser).toHaveRelationalProperty('posts', [ - entity('post', { title: 'Third post' }), - ]) - }) - - it('updates a nullable "MANY_OF" relationship to null', () => { - const { db, dictionary } = testFactory({ - user: { - id: primaryKey(String), - posts: nullable(manyOf('post')), - }, - post: { - title: primaryKey(String), - }, - }) - - const user = db.user.create({ - id: 'user-1', - posts: [ - db.post.create({ title: 'First post' }), - db.post.create({ title: 'Second post' }), - ], - }) - - const nextUser = updateEntity( - user, - { - posts: null, - }, - dictionary.user, - ) - - expect(nextUser).toHaveRelationalProperty('posts', null) - }) - - it('forbids updating a "MANY_OF" relationship to a non-array value', () => { - const { db, dictionary } = testFactory({ - user: { - id: primaryKey(String), - posts: manyOf('post'), - }, - post: { - title: primaryKey(String), - }, - }) - - const user = db.user.create({ - id: 'user-1', - posts: [ - db.post.create({ title: 'First post' }), - db.post.create({ title: 'Second post' }), - ], - }) - - expect(() => - updateEntity( - user, - { - /** - * @note The next value of the "MANY_OF" relationship - * must be an array. - */ - posts: db.post.create({ title: 'Third post' }), - }, - dictionary.user, - ), - ).toThrow( - 'Failed to update a "MANY_OF" relationship to "post" at "user.posts" (id: "user-1"): expected the next value to be an array of entities but got {"title":"Third post"}.', - ) - }) - - it('forbids updating a "MANY_OF" relationship when any member references a different model', () => { - const { db, dictionary } = testFactory({ - user: { - id: primaryKey(String), - posts: manyOf('post'), - }, - post: { - title: primaryKey(String), - }, - country: { - code: primaryKey(String), - }, - }) - - const user = db.user.create({ - id: 'user-1', - posts: [ - db.post.create({ title: 'First post' }), - db.post.create({ title: 'Second post' }), - ], - }) - - expect(() => - updateEntity( - user, - { - posts: [ - db.post.create({ title: 'Third post' }), - db.country.create({ code: 'uk' }), - ], - }, - dictionary.user, - ), - ).toThrow( - 'Failed to update a "MANY_OF" relationship to "post" at "user.posts" (id: "user-1"): expected the next value at index 1 to reference a "post" but got "country".', - ) - }) - - it('forbids updating a non-nullable "MANY_OF" relationship to null', () => { - const { db, dictionary } = testFactory({ - user: { - id: primaryKey(String), - posts: manyOf('post'), - }, - post: { - title: primaryKey(String), - }, - }) - - const user = db.user.create({ - id: 'user-1', - posts: [ - db.post.create({ title: 'First post' }), - db.post.create({ title: 'Second post' }), - ], - }) - - expect(() => - updateEntity( - user, - { - posts: null, - }, - dictionary.user, - ), - ).toThrow( - 'Failed to update a "MANY_OF" relationship to "post" at "user.posts" (id: "user-1"): cannot update a non-nullable relationship to null.', - ) - }) - - it('preserves nested relational properties after updating the parent entity', () => { - const { db, dictionary } = testFactory({ - user: { - id: primaryKey(String), - firstName: String, - address: { - billing: { - country: oneOf('country') as any, - }, - }, - }, - country: { - code: primaryKey(String), - }, - }) - - const country = db.country.create({ code: 'uk' }) - const user = db.user.create({ - id: 'user-1', - firstName: 'John', - address: { - billing: { - country, - }, - }, - }) - - const nextUser = updateEntity( - user, - { - firstName: 'Wade', - }, - dictionary.user, - ) - - expect(nextUser.address.billing).toHaveRelationalProperty( - 'country', - country, - ) - expect(nextUser.address.billing.country.code).toEqual('uk') - }) -}) diff --git a/tests/clear.test.ts b/tests/clear.test.ts new file mode 100644 index 00000000..17eae5b4 --- /dev/null +++ b/tests/clear.test.ts @@ -0,0 +1,25 @@ +import { Collection } from '#/src/collection.js' +import { z } from 'zod' + +const schema = z.object({ id: z.number() }) + +it('does nothing when called on an empty collection', async () => { + const users = new Collection({ schema }) + users.clear() + + expect(users.all()).toEqual([]) +}) + +it('deletes all records in the collection', async () => { + const users = new Collection({ schema }) + await users.create({ id: 1 }) + + users.clear() + expect(users.all()).toEqual([]) + + await users.create({ id: 2 }) + await users.create({ id: 3 }) + + users.clear() + expect(users.all()).toEqual([]) +}) diff --git a/tests/count.test.ts b/tests/count.test.ts new file mode 100644 index 00000000..3c8ba4ef --- /dev/null +++ b/tests/count.test.ts @@ -0,0 +1,24 @@ +import { Collection } from '#/src/collection.js' +import { z } from 'zod' + +const schema = z.object({ + id: z.number(), +}) + +it('returns 0 for a collection without any records', async () => { + const users = new Collection({ schema }) + expect(users.count()).toBe(0) +}) + +it('returns the total number of records', async () => { + const users = new Collection({ schema }) + + await users.create({ id: 1 }) + expect(users.count()).toBe(1) + + await users.create({ id: 2 }) + expect(users.count()).toBe(2) + + await users.create({ id: 3 }) + expect(users.count()).toBe(3) +}) diff --git a/tests/create-derived-property.test.ts b/tests/create-derived-property.test.ts new file mode 100644 index 00000000..e1863ff2 --- /dev/null +++ b/tests/create-derived-property.test.ts @@ -0,0 +1,28 @@ +import { Collection } from '#/src/index.js' +import { z } from 'zod' + +it('derives a value from other values', async () => { + const users = new Collection({ + schema: z + .object({ + firstName: z.string(), + lastName: z.string(), + email: z.email().optional(), + }) + .transform((user) => { + user.email = `${user.firstName.toLowerCase()}.${user.lastName.toLowerCase()}@email.com` + return user + }), + }) + + await expect( + users.create({ + firstName: 'John', + lastName: 'Doe', + }), + ).resolves.toEqual({ + firstName: 'John', + lastName: 'Doe', + email: 'john.doe@email.com', + }) +}) diff --git a/tests/create-many.test.ts b/tests/create-many.test.ts new file mode 100644 index 00000000..8e1fc007 --- /dev/null +++ b/tests/create-many.test.ts @@ -0,0 +1,14 @@ +import { Collection } from '#/src/collection.js' +import { z } from 'zod' + +const schema = z.object({ + id: z.number(), +}) + +it('creates multiple records', async () => { + const users = new Collection({ schema }) + + await expect( + users.createMany(5, (index) => ({ id: index + 1 })), + ).resolves.toEqual([{ id: 1 }, { id: 2 }, { id: 3 }, { id: 4 }, { id: 5 }]) +}) diff --git a/tests/delete-many.test.ts b/tests/delete-many.test.ts new file mode 100644 index 00000000..0f1d3c82 --- /dev/null +++ b/tests/delete-many.test.ts @@ -0,0 +1,75 @@ +import { Collection } from '#/src/collection.js' +import { Query } from '#/src/query.js' +import { z } from 'zod' + +const schema = z.object({ + id: z.number(), +}) + +it('returns an empty array if no record matches the query', async () => { + const users = new Collection({ schema }) + await users.create({ id: 1 }) + + expect(users.deleteMany((q) => q.where({ id: (id) => id > 1 }))).toEqual([]) +}) + +it('errors on empty results in a strict mode', async () => { + const users = new Collection({ schema }) + + expect(() => + users.deleteMany((q) => q.where({ id: 123 }), { strict: true }), + ).toThrow( + 'Failed to execute "deleteMany" on collection: no records found matching the query', + ) +}) + +it('deletes all matching records', async () => { + const users = new Collection({ schema }) + + await users.create({ id: 1 }) + await users.create({ id: 2 }) + await users.create({ id: 3 }) + await users.create({ id: 4 }) + + expect + .soft(users.deleteMany((q) => q.where({ id: (id) => id > 1 && id < 4 }))) + .toEqual([{ id: 2 }, { id: 3 }]) + expect.soft(users.all()).toEqual([{ id: 1 }, { id: 4 }]) +}) + +it('supports a query instance as the predicate', async () => { + const users = new Collection({ + schema: z.object({ id: z.number() }), + }) + await users.createMany(5, (index) => ({ id: index + 1 })) + + expect(users.deleteMany(new Query((user) => user.id % 2 === 0))).toEqual([ + { id: 2 }, + { id: 4 }, + ]) + expect(users.all()).toEqual([{ id: 1 }, { id: 3 }, { id: 5 }]) +}) + +it('sorts the deleted records by given criteria', async () => { + const users = new Collection({ schema: schema.extend({ name: z.string() }) }) + + await users.create({ id: 1, name: 'John' }) + await users.create({ id: 2, name: 'Sarah' }) + await users.create({ id: 3, name: 'Alice' }) + await users.create({ id: 4, name: 'Bob' }) + + expect + .soft( + users.deleteMany((q) => q.where({ id: (id) => id > 1 && id < 4 }), { + orderBy: { name: 'asc' }, + }), + ) + .toEqual([ + { id: 3, name: 'Alice' }, + { id: 2, name: 'Sarah' }, + ]) + expect.soft(users.all()).toEqual([ + { id: 1, name: 'John' }, + { id: 4, name: 'Bob' }, + ]) +}) diff --git a/tests/delete.test.ts b/tests/delete.test.ts new file mode 100644 index 00000000..16de67cf --- /dev/null +++ b/tests/delete.test.ts @@ -0,0 +1,45 @@ +import { Collection } from '#/src/collection.js' +import { Query } from '#/src/query.js' +import { z } from 'zod' + +const schema = z.object({ + id: z.number(), +}) + +it('returns undefined if no record matches the query', async () => { + const users = new Collection({ schema }) + await users.create({ id: 1 }) + + expect(users.delete((q) => q.where({ id: 3 }))).toBeUndefined() +}) + +it('errors on empty results in a strict mode', async () => { + const users = new Collection({ schema }) + + expect(() => + users.delete((q) => q.where({ id: 123 }), { strict: true }), + ).toThrow( + 'Failed to execute "delete" on collection: no record found matching the query', + ) +}) + +it('deletes a matching record', async () => { + const users = new Collection({ schema }) + + await users.create({ id: 1 }) + await users.create({ id: 2 }) + await users.create({ id: 3 }) + + expect.soft(users.delete((q) => q.where({ id: 2 }))).toEqual({ id: 2 }) + expect.soft(users.all()).toEqual([{ id: 1 }, { id: 3 }]) +}) + +it('supports a query instance as the predicate', async () => { + const users = new Collection({ + schema: z.object({ id: z.number() }), + }) + await users.createMany(5, (index) => ({ id: index + 1 })) + + expect(users.delete(new Query((user) => user.id === 4))).toEqual({ id: 4 }) + expect(users.all()).toEqual([{ id: 1 }, { id: 2 }, { id: 3 }, { id: 5 }]) +}) diff --git a/tests/extensions/persist.browser.test.ts b/tests/extensions/persist.browser.test.ts new file mode 100644 index 00000000..41d108f3 --- /dev/null +++ b/tests/extensions/persist.browser.test.ts @@ -0,0 +1,303 @@ +import { test, expect } from 'playwright.extend.js' + +test('persists records across page reloads', async ({ serve, page }) => { + const { url, evaluate } = await serve(async () => { + const z = await import('zod') + const { Collection } = await import('#/src/collection.js') + const { persist } = await import('#/src/extensions/persist.js') + + const schema = z.object({ + id: z.number(), + name: z.string(), + }) + + const users = new Collection({ schema, extensions: [persist()] }) + return { users } + }) + + await page.goto(url.href, { waitUntil: 'networkidle' }) + + await evaluate(async ({ users }) => { + await users.create({ id: 1, name: 'John' }) + }) + + await page.reload({ waitUntil: 'networkidle' }) + + await expect( + evaluate(({ users }) => { + return users.all() + }), + 'Persist the record across page reloads', + ).resolves.toEqual([{ id: 1, name: 'John' }]) + + await evaluate(async ({ users }) => { + await users.create({ id: 2, name: 'Kate' }) + }) + + await page.reload({ waitUntil: 'networkidle' }) + + await expect( + evaluate(({ users }) => { + return users.all() + }), + 'Accumulates records', + ).resolves.toEqual([ + { id: 1, name: 'John' }, + { id: 2, name: 'Kate' }, + ]) +}) + +test('persists relations defined on runtime', async ({ serve, page }) => { + const { url, evaluate } = await serve(async () => { + const z = await import('zod') + const { Collection } = await import('#/src/collection.js') + const { persist } = await import('#/src/extensions/persist.js') + + const userSchema = z.object({ + id: z.number(), + get posts() { + return z.array(postSchema).optional().default([]) + }, + }) + const postSchema = z.object({ + title: z.string(), + get author() { + return userSchema.optional() + }, + }) + + const users = new Collection({ + schema: userSchema, + extensions: [persist()], + }) + const posts = new Collection({ + schema: postSchema, + extensions: [persist()], + }) + + users.defineRelations(({ many }) => ({ + posts: many(posts), + })) + posts.defineRelations(({ one }) => ({ + author: one(users), + })) + + return { users, posts } + }) + + await page.goto(url.href, { waitUntil: 'networkidle' }) + + await evaluate(async ({ users, posts }) => { + await users.create({ + id: 1, + posts: [ + await posts.create({ title: 'First' }), + await posts.create({ title: 'Second' }), + ], + }) + }) + + await page.reload({ waitUntil: 'networkidle' }) + + await expect( + evaluate(({ users }) => { + const user = users.findFirst((q) => q.where({ id: 1 })) + return user?.posts + }), + ).resolves.toEqual([ + { title: 'First', author: expect.objectContaining({ id: 1 }) }, + { title: 'Second', author: expect.objectContaining({ id: 1 }) }, + ]) + + await expect( + evaluate(({ posts }) => { + const post = posts.findFirst((q) => q.where({ title: 'First' })) + return post?.author + }), + ).resolves.toEqual({ + id: 1, + posts: [ + expect.objectContaining({ + title: 'First', + author: expect.objectContaining({ id: 1 }), + }), + expect.objectContaining({ + title: 'Second', + author: expect.objectContaining({ id: 1 }), + }), + ], + }) +}) + +test('persists relations defined in user code', async ({ serve, page }) => { + const { url, evaluate } = await serve(async () => { + const z = await import('zod') + const { Collection } = await import('#/src/collection.js') + const { persist } = await import('#/src/extensions/persist.js') + + const userSchema = z.object({ + id: z.number(), + get posts() { + return z.array(postSchema).optional().default([]) + }, + }) + const postSchema = z.object({ + title: z.string(), + get author() { + return userSchema.optional() + }, + }) + + const users = new Collection({ + schema: userSchema, + extensions: [persist()], + }) + const posts = new Collection({ + schema: postSchema, + extensions: [persist()], + }) + + users.defineRelations(({ many }) => ({ + posts: many(posts), + })) + posts.defineRelations(({ one }) => ({ + author: one(users), + })) + + await users.create({ + id: 1, + posts: [ + await posts.create({ title: 'First' }), + await posts.create({ title: 'Second' }), + ], + }) + + return { users, posts } + }) + + await page.goto(url.href, { waitUntil: 'networkidle' }) + + await expect( + evaluate(({ users }) => { + const user = users.findFirst((q) => q.where({ id: 1 })) + return user?.posts + }), + ).resolves.toEqual([ + { title: 'First', author: expect.objectContaining({ id: 1 }) }, + { title: 'Second', author: expect.objectContaining({ id: 1 }) }, + ]) + + await page.reload({ waitUntil: 'networkidle' }) + + await expect( + evaluate(({ users }) => { + const user = users.findFirst((q) => q.where({ id: 1 })) + return user?.posts + }), + ).resolves.toEqual([ + { title: 'First', author: expect.objectContaining({ id: 1 }) }, + { title: 'Second', author: expect.objectContaining({ id: 1 }) }, + ]) +}) + +test('works in combination with `sync`', async ({ context, serve, page }) => { + const { url, evaluate } = await serve(async () => { + const z = await import('zod') + const { Collection } = await import('#/src/collection.js') + const { persist } = await import('#/src/extensions/persist.js') + const { sync } = await import('#/src/extensions/sync.js') + + const userSchema = z.object({ + id: z.number(), + get posts() { + return z.array(postSchema).optional().default([]) + }, + }) + const postSchema = z.object({ + title: z.string(), + get author() { + return userSchema.optional() + }, + }) + + const users = new Collection({ + schema: userSchema, + extensions: [sync(), persist()], + }) + const posts = new Collection({ + schema: postSchema, + extensions: [sync(), persist()], + }) + + users.defineRelations(({ many }) => ({ + posts: many(posts), + })) + posts.defineRelations(({ one }) => ({ + author: one(users), + })) + + return { users, posts } + }) + + await page.goto(url.href, { waitUntil: 'networkidle' }) + + const secondPage = await context.newPage() + await secondPage.goto(url.href, { waitUntil: 'networkidle' }) + + // Create records on one page. + await evaluate( + async ({ users, posts }) => { + await users.create({ + id: 1, + posts: [ + await posts.create({ title: 'First' }), + await posts.create({ title: 'Second' }), + ], + }) + }, + { page: secondPage }, + ) + + await expect( + evaluate(({ users }) => { + return users.findFirst((q) => q.where({ id: 1 })) + }), + 'Synchronizes records with another page', + ).resolves.toEqual({ + id: 1, + posts: [ + expect.objectContaining({ + title: 'First', + author: expect.objectContaining({ id: 1 }), + }), + expect.objectContaining({ + title: 'Second', + author: expect.objectContaining({ id: 1 }), + }), + ], + }) + + await page.bringToFront() + await page.reload({ waitUntil: 'networkidle' }) + + await page.pause() + + await expect( + evaluate(({ users }) => { + return users.findFirst((q) => q.where({ id: 1 })) + }), + 'Records survive reload', + ).resolves.toEqual({ + id: 1, + posts: [ + expect.objectContaining({ + title: 'First', + author: expect.objectContaining({ id: 1 }), + }), + expect.objectContaining({ + title: 'Second', + author: expect.objectContaining({ id: 1 }), + }), + ], + }) +}) diff --git a/tests/extensions/sync.browser.test.ts b/tests/extensions/sync.browser.test.ts new file mode 100644 index 00000000..985f1056 --- /dev/null +++ b/tests/extensions/sync.browser.test.ts @@ -0,0 +1,228 @@ +import { test, expect } from 'playwright.extend.js' + +test('syncs record creation across tabs', async ({ serve, context, page }) => { + const { url, evaluate } = await serve(async () => { + const z = await import('zod') + const { Collection } = await import('#/src/collection.js') + const { sync } = await import('#/src/extensions/sync.js') + + const schema = z.object({ + id: z.number(), + name: z.string(), + }) + + const users = new Collection({ schema, extensions: [sync()] }) + return { users } + }) + + await page.goto(url.href) + const secondPage = await context.newPage() + await secondPage.goto(url.href, { waitUntil: 'networkidle' }) + + await expect( + evaluate(async ({ users }) => { + return await users.create({ id: 1, name: 'John' }) + }), + ).resolves.toEqual({ id: 1, name: 'John' }) + + await expect( + evaluate( + async ({ users }) => { + return users.all() + }, + { page: secondPage }, + ), + ).resolves.toEqual([ + { + id: 1, + name: 'John', + }, + ]) +}) + +test('syncs record updates across tabs', async ({ serve, context, page }) => { + const { url, evaluate } = await serve(async () => { + const z = await import('zod') + const { Collection } = await import('#/src/collection.js') + const { sync } = await import('#/src/extensions/sync.js') + + const schema = z.object({ + id: z.number(), + name: z.string(), + }) + + const users = new Collection({ schema, extensions: [sync()] }) + return { users } + }) + + await page.goto(url.href) + const secondPage = await context.newPage() + await secondPage.goto(url.href, { waitUntil: 'networkidle' }) + + await expect( + evaluate(async ({ users }) => { + return await users.create({ id: 1, name: 'John' }) + }), + ).resolves.toEqual({ id: 1, name: 'John' }) + + await expect( + evaluate( + async ({ users }) => { + return await users.update((q) => q.where({ id: 1 }), { + data(user) { + user.name = 'Johnatan' + }, + }) + }, + { page: secondPage }, + ), + ).resolves.toEqual({ id: 1, name: 'Johnatan' }) + + await expect( + evaluate(async ({ users }) => { + return users.all() + }), + 'Propagates the update to extraneous tab', + ).resolves.toEqual([{ id: 1, name: 'Johnatan' }]) +}) + +test('syncs updates that use functions to derive next values', async ({ + context, + serve, + page, +}) => { + const { url, evaluate } = await serve(async () => { + const z = await import('zod') + const { Collection } = await import('#/src/collection.js') + const { sync } = await import('#/src/extensions/sync.js') + + const schema = z.object({ + id: z.number(), + name: z.string(), + }) + + const users = new Collection({ schema, extensions: [sync()] }) + return { users } + }) + + await page.goto(url.href) + const secondPage = await context.newPage() + await secondPage.goto(url.href, { waitUntil: 'networkidle' }) + + await expect( + evaluate(async ({ users }) => { + return await users.create({ id: 1, name: 'John' }) + }), + ).resolves.toEqual({ id: 1, name: 'John' }) + + await expect( + evaluate( + async ({ users }) => { + return await users.update((q) => q.where({ id: 1 }), { + data(user) { + user.name = user.name.toUpperCase() + }, + }) + }, + { page: secondPage }, + ), + ).resolves.toEqual({ id: 1, name: 'JOHN' }) + + await expect( + evaluate(async ({ users }) => { + return users.all() + }), + 'Propagates the update to extraneous tab', + ).resolves.toEqual([{ id: 1, name: 'JOHN' }]) +}) + +test('syncs updates that use a root-level `data` function', async ({ + context, + serve, + page, +}) => { + const { url, evaluate } = await serve(async () => { + const z = await import('zod') + const { Collection } = await import('#/src/collection.js') + const { sync } = await import('#/src/extensions/sync.js') + + const schema = z.object({ + id: z.number(), + name: z.string(), + }) + + const users = new Collection({ schema, extensions: [sync()] }) + return { users } + }) + + await page.goto(url.href) + const secondPage = await context.newPage() + await secondPage.goto(url.href, { waitUntil: 'networkidle' }) + + await expect( + evaluate(async ({ users }) => { + return await users.create({ id: 1, name: 'John' }) + }), + ).resolves.toEqual({ id: 1, name: 'John' }) + + await expect( + evaluate( + async ({ users }) => { + return await users.update((q) => q.where({ id: 1 }), { + data(user) { + user.name = `${user.name.toUpperCase()}${user.id}` + }, + }) + }, + { page: secondPage }, + ), + ).resolves.toEqual({ id: 1, name: 'JOHN1' }) + + await expect( + evaluate(async ({ users }) => { + return users.all() + }), + 'Propagates the update to extraneous tab', + ).resolves.toEqual([{ id: 1, name: 'JOHN1' }]) +}) + +test('syncs record deletion across tabs', async ({ serve, context, page }) => { + const { url, evaluate } = await serve(async () => { + const z = await import('zod') + const { Collection } = await import('#/src/collection.js') + const { sync } = await import('#/src/extensions/sync.js') + + const schema = z.object({ + id: z.number(), + name: z.string(), + }) + + const users = new Collection({ schema, extensions: [sync()] }) + return { users } + }) + + await page.goto(url.href) + const secondPage = await context.newPage() + await secondPage.goto(url.href, { waitUntil: 'networkidle' }) + + await expect( + evaluate(async ({ users }) => { + return await users.create({ id: 1, name: 'John' }) + }), + ).resolves.toEqual({ id: 1, name: 'John' }) + + await expect( + evaluate( + async ({ users }) => { + return users.delete((q) => q.where({ id: 1 })) + }, + { page: secondPage }, + ), + ).resolves.toEqual({ id: 1, name: 'John' }) + + await expect( + evaluate(async ({ users }) => { + return users.all() + }), + ).resolves.toEqual([]) +}) diff --git a/tests/find-first.test.ts b/tests/find-first.test.ts new file mode 100644 index 00000000..cf57be63 --- /dev/null +++ b/tests/find-first.test.ts @@ -0,0 +1,175 @@ +import { Collection } from '#/src/collection.js' +import { Query } from '#/src/query.js' +import { z } from 'zod' + +const schema = z.object({ + id: z.number(), + name: z.string().optional(), +}) + +it('returns undefined if no entry matched the query', async () => { + const users = new Collection({ schema }) + + expect(users.findFirst((q) => q.where({ id: 1 }))).toBeUndefined() + + await users.create({ id: 5, name: 'John' }) + expect(users.findFirst((q) => q.where({ id: 1 }))).toBeUndefined() +}) + +it('errors on empty results in a strict mode', async () => { + const users = new Collection({ schema }) + + expect(() => + users.findFirst((q) => q.where({ id: 1 }), { strict: true }), + ).toThrow( + 'Failed to execute "findFirst" on collection: no record found matching the query', + ) +}) + +it('returns the first record if called without any arguments', async () => { + const users = new Collection({ schema }) + const user = await users.create({ id: 1 }) + + expect(users.findFirst()).toEqual(user) +}) + +it('queries by literal value', async () => { + const users = new Collection({ schema }) + const user = await users.create({ id: 1 }) + + expect(users.findFirst((q) => q.where({ id: 1 }))).toEqual(user) +}) + +it('queries by multiple literal values', async () => { + const users = new Collection({ schema }) + const user = await users.create({ id: 1, name: 'John' }) + + expect(users.findFirst((q) => q.where({ id: 1, name: 'John' }))).toEqual(user) +}) + +it('supports a query instance as the predicate', async () => { + const users = new Collection({ + schema: z.object({ id: z.number() }), + }) + await users.createMany(5, (index) => ({ id: index + 1 })) + + expect(users.findFirst(new Query((user) => user.id === 4))).toEqual({ id: 4 }) +}) + +it('queries by multiple literal values (AND)', async () => { + const users = new Collection({ schema }) + + const user = await users.create({ id: 1, name: 'John' }) + await users.create({ id: 2, name: 'John' }) + + expect( + users.findFirst((q) => q.where({ id: 1 }).and({ name: 'John' })), + ).toEqual(user) +}) + +it('queries by multiple literal values (OR)', async () => { + const users = new Collection({ schema }) + + const firstUser = await users.create({ id: 1 }) + const secondUser = await users.create({ id: 2, name: 'Alice' }) + + expect( + users.findFirst((q) => q.where({ id: 1 }).or({ name: 'Alice' })), + 'Returns the first matching entry', + ).toEqual(firstUser) + + expect( + users.findFirst((q) => q.where({ id: 2 }).or({ name: 'Alice' })), + 'Returns the closest matching entry', + ).toEqual(secondUser) +}) + +it('queries through nested objects', async () => { + const users = new Collection({ + schema: schema.extend({ + address: z.object({ + street: z.string(), + }), + }), + }) + + const user = await users.create({ + id: 1, + address: { street: 'Main St' }, + }) + + expect( + users.findFirst((q) => q.where({ address: { street: 'Main St' } })), + ).toEqual(user) +}) + +it('queries through nested arrays', async () => { + const users = new Collection({ + schema: schema.extend({ + settings: z.object({ + favoriteNumbers: z.array(z.number()), + }), + }), + }) + + await users.create({ + id: 1, + settings: { favoriteNumbers: [1, 2, 3] }, + }) + + expect( + users.findFirst((q) => + q.where({ + settings: { favoriteNumbers: (numbers) => numbers.includes(2) }, + }), + ), + ).toBeDefined() +}) + +it('queries through array models', async () => { + const numberList = new Collection({ + schema: z.array(z.number()), + }) + + await numberList.create([1, 2, 3]) + await numberList.create([4, 5, 6]) + + expect( + numberList.findFirst((q) => q.where((arr) => arr.includes(2))), + ).toEqual([1, 2, 3]) +}) + +it('queries by nullable properties', async () => { + const users = new Collection({ + schema: z.object({ + id: z.number(), + organizationId: z.number().nullable(), + }), + }) + + await users.create({ id: 1, organizationId: null }) + await users.create({ id: 2, organizationId: 5 }) + await users.create({ id: 3, organizationId: null }) + + expect(users.findFirst((q) => q.where({ organizationId: null }))).toEqual({ + id: 1, + organizationId: null, + }) + expect( + users.findFirst((q) => q.where({ organizationId: (id) => id !== null })), + ).toEqual({ id: 2, organizationId: 5 }) +}) + +it('supports top-level record-based predicate via `q.where`', async () => { + const users = new Collection({ + schema: z.object({ id: z.number(), name: z.string() }), + }) + + const john = await users.create({ id: 1, name: 'John' }) + await users.create({ id: 2, name: 'Kate' }) + + expect(users.findFirst((q) => q.where((user) => user.id === 1))).toEqual(john) + expect( + users.findFirst((q) => q.where((user) => user.id === 123)), + ).toBeUndefined() +}) diff --git a/tests/find-many.test.ts b/tests/find-many.test.ts new file mode 100644 index 00000000..5a875122 --- /dev/null +++ b/tests/find-many.test.ts @@ -0,0 +1,123 @@ +import { Collection, Query } from '#/src/index.js' +import { z } from 'zod' + +const schema = z.object({ + id: z.number(), + name: z.string().optional(), +}) + +it('returns an empty array if no entry matched the query', async () => { + const users = new Collection({ schema }) + await users.create({ id: 1, name: 'Alice' }) + + expect(users.findMany((q) => q.where({ name: 'John' }))).toEqual([]) +}) + +it('errors on empty results in a strict mode', async () => { + const users = new Collection({ schema }) + + expect(() => + users.findMany((q) => q.where({ name: 'John' }), { strict: true }), + ).toThrow( + 'Failed to execute "findMany" on collection: no records found matching the query', + ) +}) + +it('returns all records if called without any arguments', async () => { + const users = new Collection({ schema }) + const firstUser = await users.create({ id: 1 }) + const secondUser = await users.create({ id: 2 }) + + expect(users.findMany()).toEqual([firstUser, secondUser]) +}) + +it('returns a single entry matching the query', async () => { + const users = new Collection({ schema }) + + await users.create({ id: 1, name: 'John' }) + await users.create({ id: 2, name: 'Alice' }) + + expect(users.findMany((q) => q.where({ name: 'John' }))).toEqual([ + { id: 1, name: 'John' }, + ]) +}) + +it('returns all entries matching the query', async () => { + const users = new Collection({ schema }) + + await users.create({ id: 1, name: 'John' }) + await users.create({ id: 2, name: 'Alice' }) + await users.create({ id: 3, name: 'John' }) + + expect(users.findMany((q) => q.where({ name: 'John' }))).toEqual([ + { id: 1, name: 'John' }, + { id: 3, name: 'John' }, + ]) +}) + +it('supports a query instance as the predicate', async () => { + const users = new Collection({ + schema: z.object({ id: z.number() }), + }) + await users.createMany(5, (index) => ({ id: index + 1 })) + + expect(users.findMany(new Query((user) => user.id % 2 === 0))).toEqual([ + { id: 2 }, + { id: 4 }, + ]) +}) + +it('returns all entries matching the query (OR)', async () => { + const users = new Collection({ schema }) + + await users.create({ id: 1, name: 'John' }) + await users.create({ id: 2, name: 'Alice' }) + await users.create({ id: 3, name: 'John' }) + + expect(users.findMany((q) => q.where({ id: 1 }).or({ id: 3 }))).toEqual([ + { id: 1, name: 'John' }, + { id: 3, name: 'John' }, + ]) + + expect( + users.findMany((q) => q.where({ id: 999 }).or({ id: (id) => id > 1 })), + ).toEqual([ + { id: 2, name: 'Alice' }, + { id: 3, name: 'John' }, + ]) +}) + +it('queries by nullable properties', async () => { + const users = new Collection({ + schema: z.object({ + id: z.number(), + organizationId: z.number().nullable(), + }), + }) + + await users.create({ id: 1, organizationId: null }) + await users.create({ id: 2, organizationId: 5 }) + await users.create({ id: 3, organizationId: null }) + + expect(users.findMany((q) => q.where({ organizationId: null }))).toEqual([ + { id: 1, organizationId: null }, + { id: 3, organizationId: null }, + ]) + expect( + users.findMany((q) => q.where({ organizationId: (id) => id !== null })), + ).toEqual([{ id: 2, organizationId: 5 }]) +}) + +it('supports top-level record-based predicate via `q.where`', async () => { + const users = new Collection({ + schema: z.object({ id: z.number(), name: z.string() }), + }) + + const john = await users.create({ id: 1, name: 'John' }) + const johnatan = await users.create({ id: 2, name: 'Johnatan' }) + + expect( + users.findMany((q) => q.where((user) => user.name.startsWith('John'))), + ).toEqual([john, johnatan]) + expect(users.findMany((q) => q.where((user) => user.id > 3))).toEqual([]) +}) diff --git a/tests/hooks/create.test.ts b/tests/hooks/create.test.ts new file mode 100644 index 00000000..c8c6769b --- /dev/null +++ b/tests/hooks/create.test.ts @@ -0,0 +1,83 @@ +import { Collection } from '#/src/collection.js' +import type { HookEventListener } from '#/src/hooks.js' +import { z } from 'zod' + +it('invokes the create hook when a new record is created', async () => { + const users = new Collection({ + schema: z.object({ + id: z.number(), + }), + }) + + const hook = vi.fn>() + users.hooks.on('create', hook) + + await users.create({ id: 1 }) + await users.create({ id: 2 }) + + expect.soft(hook).toHaveBeenCalledTimes(2) + expect.soft(hook).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + data: { + initialValues: { id: 1 }, + record: { id: 1 }, + }, + }), + ) + expect.soft(hook).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + data: { + initialValues: { id: 2 }, + record: { id: 2 }, + }, + }), + ) +}) + +it('differentiates between initial values and the created record', async () => { + const users = new Collection({ + schema: z.object({ + id: z.number(), + name: z.string().optional(), + subscribed: z.boolean().default(false), + }), + }) + + const hook = vi.fn>() + users.hooks.on('create', hook) + + await users.create({ id: 1 }) + await users.create({ id: 2, name: 'John' }) + await users.create({ id: 3, subscribed: true }) + + expect.soft(hook).toHaveBeenCalledTimes(3) + expect.soft(hook).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + data: { + initialValues: { id: 1 }, + record: { id: 1, subscribed: false }, + }, + }), + ) + expect.soft(hook).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + data: { + initialValues: { id: 2, name: 'John' }, + record: { id: 2, name: 'John', subscribed: false }, + }, + }), + ) + expect.soft(hook).toHaveBeenNthCalledWith( + 3, + expect.objectContaining({ + data: { + initialValues: { id: 3, subscribed: true }, + record: { id: 3, subscribed: true }, + }, + }), + ) +}) diff --git a/tests/hooks/delete.test.ts b/tests/hooks/delete.test.ts new file mode 100644 index 00000000..4907ac71 --- /dev/null +++ b/tests/hooks/delete.test.ts @@ -0,0 +1,111 @@ +import { Collection } from '#/src/collection.js' +import type { HookEventListener } from '#/src/hooks.js' +import { z } from 'zod' + +it('invokes the delete hook when a record is deleted', async () => { + const users = new Collection({ + schema: z.object({ id: z.number() }), + }) + + const hook = vi.fn>() + users.hooks.on('delete', hook) + + await users.createMany(5, (index) => ({ id: index + 1 })) + + users.delete((q) => q.where({ id: 2 })) + + expect(hook).toHaveBeenCalledOnce() + expect(hook).toHaveBeenCalledWith( + expect.objectContaining({ + data: { deletedRecord: { id: 2 } }, + }), + ) +}) + +it('invokes the delete hook in the opposite order for every deleted record', async () => { + const users = new Collection({ + schema: z.object({ id: z.number() }), + }) + + const hook = vi.fn>() + users.hooks.on('delete', hook) + + await users.createMany(5, (index) => ({ id: index + 1 })) + + users.deleteMany((q) => q.where({ id: (id) => id >= 2 && id <= 4 })) + + expect(hook).toHaveBeenCalledTimes(3) + expect(hook).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + data: { deletedRecord: { id: 4 } }, + }), + ) + expect(hook).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + data: { deletedRecord: { id: 3 } }, + }), + ) + expect(hook).toHaveBeenNthCalledWith( + 3, + expect.objectContaining({ + data: { deletedRecord: { id: 2 } }, + }), + ) +}) + +it('does not delete the record if the delete ecent is prevented', async () => { + const users = new Collection({ + schema: z.object({ id: z.number() }), + }) + + const hook = vi.fn>((event) => { + event.preventDefault() + }) + users.hooks.on('delete', hook) + + await users.createMany(3, (index) => ({ id: index + 1 })) + users.delete((q) => q.where({ id: 2 })) + expect(hook).toHaveBeenCalledOnce() + expect(users.all()).toEqual([{ id: 1 }, { id: 2 }, { id: 3 }]) +}) + +it('allows preventing the default for specific records', async () => { + const users = new Collection({ + schema: z.object({ id: z.number() }), + }) + + const hook = vi.fn>((event) => { + if (event.data.deletedRecord.id === 3) { + event.preventDefault() + } + }) + users.hooks.on('delete', hook) + + await users.createMany(5, (index) => ({ id: index + 1 })) + + users.deleteMany((q) => q.where({ id: (id) => id >= 2 && id <= 4 })) + + expect(hook).toHaveBeenCalledTimes(3) + expect(hook).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + data: { deletedRecord: { id: 4 } }, + }), + ) + expect(hook).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + data: { deletedRecord: { id: 3 } }, + }), + ) + expect(hook).toHaveBeenNthCalledWith( + 3, + expect.objectContaining({ + data: { deletedRecord: { id: 2 } }, + }), + ) + + expect(users.all()).toEqual([{ id: 1 }, { id: 3 }, { id: 5 }]) +}) diff --git a/tests/hooks/update.test.ts b/tests/hooks/update.test.ts new file mode 100644 index 00000000..d3940b99 --- /dev/null +++ b/tests/hooks/update.test.ts @@ -0,0 +1,444 @@ +import { Collection } from '#/src/collection.js' +import type { HookEventListener } from '#/src/hooks.js' +import { isRecord } from '#/src/utils.js' +import { z } from 'zod' + +it('invokes the update hook for a root property update', async () => { + const users = new Collection({ + schema: z.object({ + id: z.number(), + }), + }) + + const hook = vi.fn() + users.hooks.on('update', hook) + + const user = await users.create({ id: 1 }) + await users.update(user, { + data(user) { + user.id = 123 + }, + }) + + expect.soft(hook).toHaveBeenCalledTimes(1) + expect.soft(hook).toHaveBeenCalledWith( + expect.objectContaining({ + data: { + prevRecord: { id: 1 }, + nextRecord: { id: 123 }, + path: ['id'], + prevValue: 1, + nextValue: 123, + }, + }), + ) +}) + +it('invokes the update hook for a nested property update', async () => { + const users = new Collection({ + schema: z.object({ + billing: z.object({ + address: z.object({ + street: z.string(), + }), + }), + }), + }) + + const hook = vi.fn() + users.hooks.on('update', hook) + + const user = await users.create({ billing: { address: { street: 'Baker' } } }) + await users.update(user, { + data(user) { + user.billing.address.street = 'Sunwell' + }, + }) + + expect.soft(hook).toHaveBeenCalledTimes(1) + expect.soft(hook).toHaveBeenCalledWith( + expect.objectContaining({ + data: { + prevRecord: { billing: { address: { street: 'Baker' } } }, + nextRecord: { billing: { address: { street: 'Sunwell' } } }, + path: ['billing', 'address', 'street'], + prevValue: 'Baker', + nextValue: 'Sunwell', + }, + }), + ) +}) + +it('invokes the update hook when updating a relational key', async () => { + const countrySchema = z.object({ code: z.string() }) + const users = new Collection({ + schema: z.object({ country: countrySchema.optional() }), + }) + const countries = new Collection({ schema: countrySchema }) + users.defineRelations(({ one }) => ({ + country: one(countries), + })) + + const hook = vi.fn>((event) => { + expect.soft(isRecord(event.data.prevRecord)).toBe(true) + expect.soft(isRecord(event.data.nextRecord)).toBe(true) + }) + users.hooks.on('update', hook) + + const user = await users.create({ + country: await countries.create({ code: 'us' }), + }) + await users.update(user, { + async data(user) { + user.country = await countries.create({ code: 'uk' }) + }, + }) + + expect.soft(hook).toHaveBeenCalledTimes(1) + expect.soft(hook).toHaveBeenCalledWith( + expect.objectContaining({ + data: { + prevRecord: { country: { code: 'us' } }, + nextRecord: { country: { code: 'uk' } }, + path: ['country'], + prevValue: { code: 'us' }, + nextValue: { code: 'uk' }, + }, + }), + ) +}) + +it('treats updates through relational keys like foreign record updates', async () => { + const countrySchema = z.object({ code: z.string() }) + const users = new Collection({ + schema: z.object({ country: countrySchema.optional() }), + }) + const countries = new Collection({ schema: countrySchema }) + users.defineRelations(({ one }) => ({ + country: one(countries), + })) + + const usersHook = vi.fn>( + (event) => { + expect.soft(isRecord(event.data.prevRecord)).toBe(true) + expect.soft(isRecord(event.data.nextRecord)).toBe(true) + }, + ) + const countriesHook = vi.fn>( + (event) => { + expect.soft(isRecord(event.data.prevRecord)).toBe(true) + expect.soft(isRecord(event.data.nextRecord)).toBe(true) + }, + ) + users.hooks.on('update', usersHook) + countries.hooks.on('update', countriesHook) + + const user = await users.create({ + country: await countries.create({ code: 'us' }), + }) + await users.update(user, { + data(user) { + user.country!.code = 'uk' + }, + }) + + expect.soft(usersHook).not.toHaveBeenCalled() + + expect.soft(countriesHook).toHaveBeenCalledTimes(1) + expect.soft(countriesHook).toHaveBeenCalledWith( + expect.objectContaining({ + data: { + prevRecord: { code: 'us' }, + nextRecord: { code: 'uk' }, + path: ['code'], + prevValue: 'us', + nextValue: 'uk', + }, + }), + ) +}) + +it('combines the owner updates and the relational key update', async () => { + const countrySchema = z.object({ code: z.string() }) + const users = new Collection({ + schema: z.object({ + name: z.string(), + country: countrySchema.optional(), + }), + }) + const countries = new Collection({ schema: countrySchema }) + users.defineRelations(({ one }) => ({ + country: one(countries), + })) + + const usersHook = vi.fn>( + (event) => { + expect.soft(isRecord(event.data.prevRecord)).toBe(true) + expect.soft(isRecord(event.data.nextRecord)).toBe(true) + }, + ) + const countriesHook = vi.fn>( + (event) => { + expect.soft(isRecord(event.data.prevRecord)).toBe(true) + expect.soft(isRecord(event.data.nextRecord)).toBe(true) + }, + ) + users.hooks.on('update', usersHook) + countries.hooks.on('update', countriesHook) + + const user = await users.create({ + name: 'John', + country: await countries.create({ code: 'us' }), + }) + await users.update(user, { + async data(user) { + user.name = 'Johnatan' + user.country = await countries.create({ code: 'uk' }) + }, + }) + + expect.soft(usersHook).toHaveBeenCalledTimes(2) + expect.soft(usersHook).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + data: { + prevRecord: { name: 'John', country: { code: 'us' } }, + nextRecord: { name: 'Johnatan', country: { code: 'uk' } }, + path: ['name'], + prevValue: 'John', + nextValue: 'Johnatan', + }, + }), + ) + expect.soft(usersHook).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + data: { + prevRecord: { name: 'John', country: { code: 'us' } }, + nextRecord: { name: 'Johnatan', country: { code: 'uk' } }, + path: ['country'], + prevValue: { code: 'us' }, + nextValue: { code: 'uk' }, + }, + }), + ) +}) + +it('combines the owner updates and deep foreign record updates', async () => { + const countrySchema = z.object({ code: z.string() }) + const users = new Collection({ + schema: z.object({ + name: z.string(), + country: countrySchema.optional(), + }), + }) + const countries = new Collection({ schema: countrySchema }) + users.defineRelations(({ one }) => ({ + country: one(countries), + })) + + const usersHook = vi.fn>( + (event) => { + expect.soft(isRecord(event.data.prevRecord)).toBe(true) + expect.soft(isRecord(event.data.nextRecord)).toBe(true) + }, + ) + const countriesHook = vi.fn>( + (event) => { + expect.soft(isRecord(event.data.prevRecord)).toBe(true) + expect.soft(isRecord(event.data.nextRecord)).toBe(true) + }, + ) + users.hooks.on('update', usersHook) + countries.hooks.on('update', countriesHook) + + const user = await users.create({ + name: 'John', + country: await countries.create({ code: 'us' }), + }) + await users.update(user, { + data(user) { + user.name = 'Johnatan' + user.country!.code = 'uk' + }, + }) + + expect.soft(usersHook).toHaveBeenCalledTimes(1) + expect.soft(usersHook).toHaveBeenCalledWith( + expect.objectContaining({ + data: { + prevRecord: { name: 'John', country: { code: 'us' } }, + /** + * @note Relational keys will point to the latest values + * even if observed through an unrelated update (e.g. "name"). + */ + nextRecord: { name: 'Johnatan', country: { code: 'uk' } }, + path: ['name'], + prevValue: 'John', + nextValue: 'Johnatan', + }, + }), + ) + + expect.soft(countriesHook).toHaveBeenCalledTimes(1) + expect.soft(countriesHook).toHaveBeenCalledWith( + expect.objectContaining({ + data: { + prevRecord: { code: 'us' }, + nextRecord: { code: 'uk' }, + path: ['code'], + prevValue: 'us', + nextValue: 'uk', + }, + }), + ) +}) + +it('invokes the update hook for each change in the root-level draft', async () => { + const users = new Collection({ + schema: z.object({ + id: z.number(), + name: z.string().optional(), + }), + }) + const hook = vi.fn() + users.hooks.on('update', hook) + + const user = await users.create({ id: 1 }) + await users.update(user, { + data(user) { + user.id = 2 + user.name = 'John' + }, + }) + + expect.soft(hook).toHaveBeenCalledTimes(2) + expect.soft(hook).toHaveBeenNthCalledWith( + 1, + expect.objectContaining({ + data: { + prevRecord: { id: 1 }, + nextRecord: { id: 2, name: 'John' }, + path: ['id'], + prevValue: 1, + nextValue: 2, + }, + }), + ) + expect.soft(hook).toHaveBeenNthCalledWith( + 2, + expect.objectContaining({ + data: { + prevRecord: { id: 1 }, + nextRecord: { id: 2, name: 'John' }, + path: ['name'], + prevValue: undefined, + nextValue: 'John', + }, + }), + ) +}) + +it('invokes the update hook for each change in a nested draft', async () => { + const users = new Collection({ + schema: z.object({ + id: z.number(), + address: z.object({ + street: z.object({ + name: z.string(), + houseNumber: z.number(), + }), + }), + }), + }) + + const hook = vi.fn() + users.hooks.on('update', hook) + + const user = await users.create({ + id: 1, + address: { + street: { + name: 'Baker', + houseNumber: 123, + }, + }, + }) + + await users.update(user, { + data(user) { + user.address.street = { + name: 'Sunwell', + houseNumber: 456, + } + }, + }) + + expect.soft(hook).toHaveBeenCalledTimes(1) + expect.soft(hook).toHaveBeenCalledWith( + expect.objectContaining({ + data: { + prevRecord: { + id: 1, + address: { street: { name: 'Baker', houseNumber: 123 } }, + }, + nextRecord: { + id: 1, + address: { street: { name: 'Sunwell', houseNumber: 456 } }, + }, + path: ['address', 'street'], + prevValue: { name: 'Baker', houseNumber: 123 }, + nextValue: { name: 'Sunwell', houseNumber: 456 }, + }, + }), + ) +}) + +it('prevents certain updates via the update hook', async () => { + const users = new Collection({ + schema: z.object({ + id: z.number(), + address: z.object({ + street: z.object({ + name: z.string(), + houseNumber: z.number(), + }), + }), + }), + }) + + const hook = vi.fn>((event) => { + if (event.data.path.join('.') === 'address.street.name') { + event.preventDefault() + } + }) + users.hooks.on('update', hook) + + const user = await users.create({ + id: 1, + address: { + street: { + name: 'Baker', + houseNumber: 123, + }, + }, + }) + + await expect( + users.update(user, { + data(user) { + user.id = 2 + user.address.street.name = 'Sunwell' + user.address.street.houseNumber = 456 + }, + }), + ).resolves.toEqual({ + id: 2, + address: { + street: { + name: 'Baker', + houseNumber: 456, + }, + }, + }) +}) diff --git a/tests/nullable.test.ts b/tests/nullable.test.ts new file mode 100644 index 00000000..5d2ccc25 --- /dev/null +++ b/tests/nullable.test.ts @@ -0,0 +1,146 @@ +import { Collection } from '#/src/collection.js' +import { z } from 'zod' + +it('supports null as initial value for nullable properties', async () => { + const users = new Collection({ + schema: z.object({ id: z.number().nullable() }), + }) + + await expect(users.create({ id: null })).resolves.toEqual({ id: null }) +}) + +it('supports searching by nullable properties', async () => { + const users = new Collection({ + schema: z.object({ id: z.number(), name: z.string().nullable() }), + }) + await users.create({ id: 1, name: null }) + await users.create({ id: 2, name: 'John' }) + await users.create({ id: 3, name: 'Kate' }) + await users.create({ id: 4, name: null }) + + expect(users.findFirst((q) => q.where({ name: null }))).toEqual({ + id: 1, + name: null, + }) + expect( + users.findFirst((q) => q.where({ name: (name) => name !== null })), + ).toEqual({ + id: 2, + name: 'John', + }) + + expect(users.findMany((q) => q.where({ name: null }))).toEqual([ + { + id: 1, + name: null, + }, + { + id: 4, + name: null, + }, + ]) + expect( + users.findMany((q) => q.where({ name: (name) => name !== null })), + ).toEqual([ + { + id: 2, + name: 'John', + }, + { + id: 3, + name: 'Kate', + }, + ]) +}) + +it('supports updating nullable properties', async () => { + const users = new Collection({ + schema: z.object({ id: z.number().nullable() }), + }) + await users.create({ id: null }) + + await expect( + users.update((q) => q.where({ id: null }), { + data(user) { + user.id = 123 + }, + }), + ).resolves.toEqual({ + id: 123, + }) + expect(users.findFirst((q) => q.where({ id: 123 }))).toEqual({ id: 123 }) +}) + +it('supports nullable one-to-one relation', async () => { + const countrySchema = z.object({ code: z.string() }) + const userSchema = z.object({ + id: z.number(), + country: countrySchema.nullable(), + }) + + const users = new Collection({ schema: userSchema }) + const countries = new Collection({ schema: countrySchema }) + + await expect( + users.create({ id: 1, country: null }), + 'Supports null as initial value for nullable relations', + ).resolves.toEqual({ + id: 1, + country: null, + }) + + await expect( + users.update((q) => q.where({ country: null }), { + async data(user) { + user.country = await countries.create({ code: 'uk' }) + }, + }), + 'Supports updating a nullable relation to a value', + ).resolves.toEqual({ id: 1, country: { code: 'uk' } }) + + await expect( + users.update((q) => q.where({ country: { code: 'uk' } }), { + data(user) { + user.country = null + }, + }), + 'Supports updating a nullable relation to null', + ).resolves.toEqual({ id: 1, country: null }) +}) + +it('supports nullable one-to-many relation', async () => { + const postSchema = z.object({ title: z.string() }) + const userSchema = z.object({ + id: z.number(), + posts: z.array(postSchema).nullable(), + }) + + const users = new Collection({ schema: userSchema }) + const posts = new Collection({ schema: postSchema }) + + await expect( + users.create({ id: 1, posts: null }), + 'Supports null as initial value for nullable relations', + ).resolves.toEqual({ + id: 1, + posts: null, + }) + + await expect( + users.update((q) => q.where({ posts: null }), { + async data(user) { + user.posts = [await posts.create({ title: 'First' })] + }, + }), + 'Supports updating a nullable relation to a value', + ).resolves.toEqual({ id: 1, posts: [{ title: 'First' }] }) + + await expect( + users.update((q) => q.where({ posts: { title: 'First' } }), { + data(user) { + user.posts = null + }, + }), + 'Supports updating a nullable relation to null', + ).resolves.toEqual({ id: 1, posts: null }) +}) diff --git a/tests/pagination-cursor.test.ts b/tests/pagination-cursor.test.ts new file mode 100644 index 00000000..95edf1e5 --- /dev/null +++ b/tests/pagination-cursor.test.ts @@ -0,0 +1,117 @@ +import { Collection } from '#/src/collection.js' +import { z } from 'zod' + +const userSchema = z.object({ + id: z.number(), +}) + +it('returns an empty array if the cursor points at a deleted record', async () => { + const users = new Collection({ schema: userSchema }) + await users.createMany(5, (index) => ({ + id: index + 1, + })) + + const cursor = users.findFirst((q) => q.where({ id: 5 }))! + users.delete(cursor) + + expect( + users.findMany(undefined, { cursor }), + 'Returns an empty array if the cursor points at a deleted record', + ).toEqual([]) +}) + +it('returns all the matching records after the cursor', async () => { + const users = new Collection({ schema: userSchema }) + await users.createMany(10, (index) => ({ + id: index + 1, + })) + + const cursor = users.findFirst((q) => q.where({ id: 7 }))! + + expect( + users.findMany(undefined, { cursor }), + 'Supports match-all queries', + ).toEqual([{ id: 7 }, { id: 8 }, { id: 9 }, { id: 10 }]) +}) + +it('returns the `take` number of results after the cursor', async () => { + const users = new Collection({ schema: userSchema }) + await users.createMany(10, (index) => ({ + id: index + 1, + })) + + const cursor = users.findFirst((q) => q.where({ id: 7 }))! + + expect( + users.findMany(undefined, { cursor, take: 3 }), + 'Supports match-all queries', + ).toEqual([{ id: 7 }, { id: 8 }, { id: 9 }]) +}) + +it('supports skipping the cursor', async () => { + const users = new Collection({ schema: userSchema }) + await users.createMany(10, (index) => ({ + id: index + 1, + })) + + const cursor = users.findFirst((q) => q.where({ id: 7 }))! + + expect( + users.findMany(undefined, { cursor, skip: 1, take: 3 }), + 'Supports match-all queries', + ).toEqual([{ id: 8 }, { id: 9 }, { id: 10 }]) +}) + +it('supports negative values for `take`', async () => { + const users = new Collection({ schema: userSchema }) + await users.createMany(10, (index) => ({ + id: index + 1, + })) + + const cursor = users.findFirst((q) => q.where({ id: 10 })) + + expect( + users.findMany(undefined, { + cursor, + take: -3, + }), + ).toEqual([{ id: 10 }, { id: 9 }, { id: 8 }]) + + expect( + users.findMany(undefined, { + cursor, + skip: 1, + take: -3, + }), + 'Supports skipping the cursor', + ).toEqual([{ id: 9 }, { id: 8 }, { id: 7 }]) + + expect( + users.findMany((q) => q.where({ id: (id) => id > 2 }), { + cursor: users.findFirst((q) => q.where({ id: 8 })), + take: -3, + }), + ).toEqual([{ id: 8 }, { id: 7 }, { id: 6 }]) + + expect( + users.findMany((q) => q.where({ id: (id) => id > 2 }), { + cursor: users.findFirst((q) => q.where({ id: 8 })), + skip: 1, + take: -3, + }), + ).toEqual([{ id: 7 }, { id: 6 }, { id: 5 }]) + + expect( + users.findMany((q) => q.where({ id: (id) => id > 2 }), { + cursor: users.findFirst((q) => q.where({ id: 3 })), + take: -3, + }), + ).toEqual([{ id: 3 }]) + + expect( + users.findMany((q) => q.where({ id: (id) => id > 2 }), { + cursor: users.findFirst((q) => q.where({ id: 2 })), + take: -3, + }), + ).toEqual([]) +}) diff --git a/tests/pagination-offset.test.ts b/tests/pagination-offset.test.ts new file mode 100644 index 00000000..55a89d49 --- /dev/null +++ b/tests/pagination-offset.test.ts @@ -0,0 +1,181 @@ +import { Collection } from '#/src/collection.js' +import { z } from 'zod' + +const userSchema = z.object({ + id: z.number(), +}) + +it('ignores `takes` for non-matching queries', async () => { + const users = new Collection({ schema: userSchema }) + + expect( + users.findMany((q) => q.where({ id: 5000 }), { take: 3 }), + 'Supports non-matching queries', + ).toEqual([]) +}) + +it('returns the `take` number of results', async () => { + const users = new Collection({ schema: userSchema }) + await users.createMany(10, (index) => ({ + id: index + 1, + })) + + expect( + users.findMany(undefined, { take: 3 }), + 'Supports match-all queries', + ).toEqual([{ id: 1 }, { id: 2 }, { id: 3 }]) + + expect( + users.findMany((q) => q.where({ id: (id) => id > 2 }), { take: 3 }), + 'Supports matching queries', + ).toEqual([{ id: 3 }, { id: 4 }, { id: 5 }]) +}) + +it('returns the find results as-is if they are fewer than the `take` value', async () => { + const users = new Collection({ schema: userSchema }) + await users.createMany(3, (index) => ({ + id: index + 1, + })) + + expect( + users.findMany(undefined, { take: 10 }), + 'Supports match-all queries', + ).toEqual([{ id: 1 }, { id: 2 }, { id: 3 }]) + + expect( + users.findMany((q) => q.where({ id: 2 }), { take: 10 }), + 'Supports regular queries', + ).toEqual([{ id: 2 }]) +}) + +it('skips the provided number of results', async () => { + const users = new Collection({ schema: userSchema }) + await users.createMany(10, (index) => ({ + id: index + 1, + })) + + expect( + users.findMany(undefined, { skip: 5, take: 3 }), + 'Supports match-all queries', + ).toEqual([{ id: 6 }, { id: 7 }, { id: 8 }]) + + expect( + users.findMany((q) => q.where({ id: (id) => id > 2 }), { + skip: 1, + take: 1, + }), + 'Supports regular queries', + ).toEqual([{ id: 4 }]) + + expect( + users.findMany((q) => q.where({ id: 5000 }), { skip: 1, take: 3 }), + 'Supports non-matching queries', + ).toEqual([]) +}) + +it('treats `skip` as slice if `take` was not provided', async () => { + const users = new Collection({ schema: userSchema }) + await users.createMany(10, (index) => ({ + id: index + 1, + })) + + expect( + users.findMany(undefined, { skip: 7 }), + 'Supports match-all queries', + ).toEqual([{ id: 8 }, { id: 9 }, { id: 10 }]) + + expect( + users.findMany((q) => q.where({ id: (id) => id > 2 }), { + skip: 5, + }), + 'Supports regular queries', + ).toEqual([{ id: 8 }, { id: 9 }, { id: 10 }]) +}) + +it('returns an empty array if all the results were skipped', async () => { + const users = new Collection({ schema: userSchema }) + await users.createMany(10, (index) => ({ + id: index + 1, + })) + + expect( + users.findMany(undefined, { skip: 10, take: 3 }), + 'Supports match-all queries', + ).toEqual([]) + + expect( + users.findMany((q) => q.where({ id: (id) => id > 2 }), { + skip: 8, + take: 1, + }), + 'Supports regular queries', + ).toEqual([]) +}) + +it('throws if providing an invalid value for `skip`', async () => { + const users = new Collection({ schema: userSchema }) + await users.createMany(10, (index) => ({ + id: index + 1, + })) + + expect(() => users.findMany(undefined, { skip: -1 })).toThrow( + 'Failed to query the collection: expected the "skip" pagination option to be a number larger or equal to 0 but got -1', + ) + + expect(() => + users.findMany(undefined, { + // @ts-expect-error Intentionally invalid value. + skip: false, + }), + ).toThrow( + 'Failed to query the collection: expected the "skip" pagination option to be a number larger or equal to 0 but got false', + ) + + expect(() => + users.findMany(undefined, { + // @ts-expect-error Intentionally invalid value. + skip: null, + }), + ).toThrow( + 'Failed to query the collection: expected the "skip" pagination option to be a number larger or equal to 0 but got null', + ) + + expect(() => + users.findMany(undefined, { + // @ts-expect-error Intentionally invalid value. + skip: 'invalid', + }), + ).toThrow( + 'Failed to query the collection: expected the "skip" pagination option to be a number larger or equal to 0 but got "invalid"', + ) +}) + +it('supports negative values for `take`', async () => { + const users = new Collection({ schema: userSchema }) + await users.createMany(10, (index) => ({ + id: index + 1, + })) + + expect( + users.findMany(undefined, { take: -3 }), + 'Returns the last n results if `skip` is not provided', + ).toEqual([{ id: 10 }, { id: 9 }, { id: 8 }]) + + expect(users.findMany(undefined, { skip: 3, take: -3 })).toEqual([ + { id: 7 }, + { id: 6 }, + { id: 5 }, + ]) + + expect( + users.findMany((q) => q.where({ id: (id) => id > 2 }), { take: -3 }), + 'Does not loop the results', + ).toEqual([{ id: 10 }, { id: 9 }, { id: 8 }]) + + expect( + users.findMany((q) => q.where({ id: (id) => id > 2 }), { + skip: 3, + take: -3, + }), + ).toEqual([{ id: 7 }, { id: 6 }, { id: 5 }]) +}) diff --git a/tests/query-and.test.ts b/tests/query-and.test.ts new file mode 100644 index 00000000..1649566f --- /dev/null +++ b/tests/query-and.test.ts @@ -0,0 +1,32 @@ +import { Collection } from '#/src/index.js' +import { z } from 'zod' + +const schema = z.object({ + id: z.number(), + name: z.string(), +}) + +it('supports AND conditions when querying', async () => { + const users = new Collection({ schema }) + await users.create({ id: 1, name: 'John' }) + await users.create({ id: 2, name: 'Alice' }) + await users.create({ id: 3, name: 'Bob' }) + + expect( + users.findFirst((q) => + q.where({ id: (id) => id > 1 }).and({ name: 'Bob' }), + ), + ).toEqual({ + id: 3, + name: 'Bob', + }) + + expect( + users.findFirst((q) => + q.and(q.where({ id: (id) => id > 1 }), q.where({ name: 'Bob' })), + ), + ).toEqual({ + id: 3, + name: 'Bob', + }) +}) diff --git a/tests/query-or.test.ts b/tests/query-or.test.ts new file mode 100644 index 00000000..8e3be652 --- /dev/null +++ b/tests/query-or.test.ts @@ -0,0 +1,42 @@ +import { Collection } from '#/src/index.js' +import { z } from 'zod' + +const schema = z.object({ + id: z.number(), + name: z.string(), +}) + +it('supports OR conditions when querying', async () => { + const users = new Collection({ schema }) + await users.create({ id: 1, name: 'John' }) + await users.create({ id: 2, name: 'Alice' }) + await users.create({ id: 3, name: 'Bob' }) + + expect( + users.findMany((q) => q.where({ id: (id) => id > 1 }).or({ name: 'Bob' })), + ).toEqual([ + { + id: 2, + name: 'Alice', + }, + { + id: 3, + name: 'Bob', + }, + ]) + + expect( + users.findMany((q) => + q.or(q.where({ id: (id) => id > 1 }), q.where({ name: 'Bob' })), + ), + ).toEqual([ + { + id: 2, + name: 'Alice', + }, + { + id: 3, + name: 'Bob', + }, + ]) +}) diff --git a/tests/query.test.ts b/tests/query.test.ts new file mode 100644 index 00000000..1276ac93 --- /dev/null +++ b/tests/query.test.ts @@ -0,0 +1,62 @@ +import { Query } from '#/src/index.js' + +it('returns true when testing against a matching value', () => { + expect(new Query((value) => value === 123).test(123)).toBe(true) + expect( + new Query<{ id: number }>((value) => value.id === 123).test({ id: 123 }), + ).toBe(true) + expect(new Query<{ id: number }>().where({ id: 123 }).test({ id: 123 })).toBe( + true, + ) +}) + +it('returns false when testing against a non-matching value', () => { + expect(new Query((value) => value === 123).test(1)).toBe(false) + expect( + new Query<{ id: number }>((value) => value.id === 123).test({ id: 1 }), + ).toBe(false) + expect(new Query<{ id: number }>().where({ id: 123 }).test({ id: 1 })).toBe( + false, + ) +}) + +it('combines predicates under an OR logic', () => { + expect( + new Query<{ id: number }>((value) => value.id === 123) + .or({ id: 456 }) + .test({ id: 123 }), + ).toBe(true) + expect( + new Query<{ id: number }>((value) => value.id === 123) + .or({ id: 456 }) + .test({ id: 456 }), + ).toBe(true) + + expect( + new Query<{ id: number }>((value) => value.id === 123) + .or({ id: 456 }) + .test({ id: 1 }), + ).toBe(false) +}) + +it('combines predicates under an AND logic', () => { + expect( + new Query<{ id: number; name: string }>() + .where({ id: 456 }) + .and({ name: 'John' }) + .test({ id: 456, name: 'John' }), + ).toBe(true) + + expect( + new Query<{ id: number; name: string }>() + .where({ id: 456 }) + .and({ name: 'John' }) + .test({ id: 123, name: 'John' }), + ).toBe(false) + expect( + new Query<{ id: number; name: string }>() + .where({ id: 456 }) + .and({ name: 'John' }) + .test({ id: 456, name: 'Kate' }), + ).toBe(false) +}) diff --git a/tests/regressions/348-nested-relations.test.ts b/tests/regressions/348-nested-relations.test.ts new file mode 100644 index 00000000..06c942f6 --- /dev/null +++ b/tests/regressions/348-nested-relations.test.ts @@ -0,0 +1,40 @@ +import { z } from 'zod' +import { Collection } from '#/src/collection.js' + +it('creates a record with a nested relation through another model', async () => { + const contactSchema = z.object({ email: z.email() }) + const userSchema = z.object({ + id: z.number(), + contact: contactSchema, + }) + const postSchema = z.object({ + title: z.string(), + author: userSchema, + }) + + const contacts = new Collection({ schema: contactSchema }) + const users = new Collection({ schema: userSchema }) + const posts = new Collection({ schema: postSchema }) + + users.defineRelations(({ one }) => ({ + contact: one(contacts), + })) + posts.defineRelations(({ one }) => ({ + author: one(users), + })) + + const contact = await contacts.create({ email: 'john@example.com' }) + expect(contact).toEqual({ email: 'john@example.com' }) + + const user = await users.create({ id: 1, contact }) + expect(user).toEqual({ id: 1, contact: { email: 'john@example.com' } }) + + const post = await posts.create({ title: 'First', author: user }) + expect(post).toEqual({ + title: 'First', + author: { + id: 1, + contact: { email: 'john@example.com' }, + }, + }) +}) diff --git a/tests/relations/many-to-many.test.ts b/tests/relations/many-to-many.test.ts new file mode 100644 index 00000000..4e1e3007 --- /dev/null +++ b/tests/relations/many-to-many.test.ts @@ -0,0 +1,242 @@ +import { Collection } from '#/src/collection.js' +import { z } from 'zod' + +const userSchema = z.object({ + id: z.number(), + get posts() { + return z.array(postSchema) + }, +}) + +const postSchema = z.object({ + title: z.string(), + get authors() { + return z.array(userSchema).optional() + }, +}) + +it('supports a many-to-many relation', async () => { + const users = new Collection({ schema: userSchema }) + const posts = new Collection({ schema: postSchema }) + + users.defineRelations(({ many }) => ({ + posts: many(posts), + })) + posts.defineRelations(({ many }) => ({ + authors: many(users), + })) + + const firstPost = await posts.create({ title: 'First' }) + const secondPost = await posts.create({ title: 'Second' }) + + const firstUser = await users.create({ + id: 1, + posts: [firstPost, secondPost], + }) + const secondUser = await users.create({ + id: 2, + posts: [firstPost, secondPost], + }) + + expect(firstUser.posts).toEqual([ + { title: 'First', authors: [firstUser, secondUser] }, + { title: 'Second', authors: [firstUser, secondUser] }, + ]) + expect(secondUser.posts).toEqual([ + { title: 'First', authors: [firstUser, secondUser] }, + { title: 'Second', authors: [firstUser, secondUser] }, + ]) +}) + +it('respects updates of foreign records', async () => { + const users = new Collection({ schema: userSchema }) + const posts = new Collection({ schema: postSchema }) + + users.defineRelations(({ many }) => ({ + posts: many(posts), + })) + posts.defineRelations(({ many }) => ({ + authors: many(users), + })) + + const firstPost = await posts.create({ title: 'First' }) + const secondPost = await posts.create({ title: 'Second' }) + + const firstUser = await users.create({ + id: 1, + posts: [firstPost, secondPost], + }) + const secondUser = await users.create({ + id: 2, + posts: [firstPost, secondPost], + }) + + // Users reflect updates of posts. + await posts.update((q) => q.where({ title: 'First' }), { + data(post) { + post.title = 'Updated' + }, + }) + + expect(firstUser.posts[0], 'Updates references to foreign records').toEqual({ + title: 'Updated', + authors: [firstUser, secondUser], + }) + expect(secondUser.posts[0], 'Updates references to foreign records').toEqual({ + title: 'Updated', + authors: [firstUser, secondUser], + }) + + // Posts reflect updates of users. + const updatedSecondUser = await users.update((q) => q.where({ id: 2 }), { + data(user) { + user.id = 20 + }, + }) + expect(firstPost.authors).toEqual([firstUser, updatedSecondUser]) + expect(secondPost.authors).toEqual([firstUser, updatedSecondUser]) +}) + +it('scopes a nested many-to-many relation update to the targeted record', async () => { + const users = new Collection({ schema: userSchema }) + const posts = new Collection({ schema: postSchema }) + + users.defineRelations(({ many }) => ({ + posts: many(posts), + })) + posts.defineRelations(({ many }) => ({ + authors: many(users), + })) + + const firstPost = await posts.create({ title: 'First' }) + const secondPost = await posts.create({ title: 'Second' }) + + const firstUser = await users.create({ id: 1, posts: [firstPost] }) + await users.create({ id: 2, posts: [secondPost] }) + + await users.update(firstUser, { + data(user) { + user.posts[0]!.title = 'Updated' + }, + }) + + expect(posts.all().map((post) => post.title)).toEqual(['Updated', 'Second']) +}) + +it('creates a self referencing many-to-many relation', async () => { + const userSchema = z.object({ + id: z.number(), + get children() { + return z.array(userSchema).optional() + }, + get parents() { + return z.array(userSchema).optional() + }, + }) + + const users = new Collection({ schema: userSchema }) + + users.defineRelations(({ many }) => ({ + children: many(users, { role: 'hierarchy' }), + parents: many(users, { role: 'hierarchy' }), + })) + + { + const childOne = await users.create({ + id: 1, + }) + + const parentOne = await users.create({ + id: 2, + children: [childOne], + }) + + expect + .soft(parentOne.children) + .toEqual([expect.objectContaining({ id: 1 })]) + expect.soft(parentOne.parents).toEqual([]) + + expect.soft(childOne.children).toEqual([]) + expect.soft(childOne.parents).toEqual([expect.objectContaining({ id: 2 })]) + } + + { + const parentTwo = await users.create({ id: 3 }) + const childTwo = await users.create({ id: 4, parents: [parentTwo] }) + + expect + .soft(parentTwo.children) + .toEqual([expect.objectContaining({ id: 4 })]) + expect.soft(parentTwo.parents).toEqual([]) + + expect.soft(childTwo.children).toEqual([]) + expect.soft(childTwo.parents).toEqual([expect.objectContaining({ id: 3 })]) + } +}) + +it('updates a self referencing many-to-many relation', async () => { + const userSchema = z.object({ + id: z.number(), + get children() { + return z.array(userSchema).optional() + }, + get parents() { + return z.array(userSchema).optional() + }, + }) + + const users = new Collection({ schema: userSchema }) + + users.defineRelations(({ many }) => ({ + children: many(users, { role: 'hierarchy' }), + parents: many(users, { role: 'hierarchy' }), + })) + + const childOne = await users.create({ id: 1 }) + const childTwo = await users.create({ id: 2 }) + const parent = await users.create({ id: 3, children: [childOne] }) + + await users.update(parent, { + data(draft) { + draft.children = [childTwo] + }, + }) + + expect.soft(parent.children).toEqual([expect.objectContaining({ id: 2 })]) + expect.soft(parent.parents).toEqual([]) + expect.soft(childOne.parents).toEqual([]) + expect.soft(childTwo.parents).toEqual([expect.objectContaining({ id: 3 })]) +}) + +it('resolves a cyclic self referencing many-to-many relation', async () => { + const userSchema = z.object({ + id: z.number(), + get children() { + return z.array(userSchema).optional() + }, + get parents() { + return z.array(userSchema).optional() + }, + }) + + const users = new Collection({ schema: userSchema }) + + users.defineRelations(({ many }) => ({ + children: many(users, { role: 'hierarchy' }), + parents: many(users, { role: 'hierarchy' }), + })) + + const alice = await users.create({ id: 1 }) + const bob = await users.create({ id: 2, parents: [alice] }) + + await users.update(alice, { + data(draft) { + draft.parents = [bob] + }, + }) + + expect.soft(alice.parents).toEqual([expect.objectContaining({ id: 2 })]) + expect.soft(alice.children).toEqual([expect.objectContaining({ id: 2 })]) + expect.soft(bob.parents).toEqual([expect.objectContaining({ id: 1 })]) + expect.soft(bob.children).toEqual([expect.objectContaining({ id: 1 })]) +}) diff --git a/tests/relations/many-to-one.test.ts b/tests/relations/many-to-one.test.ts new file mode 100644 index 00000000..1bdc120b --- /dev/null +++ b/tests/relations/many-to-one.test.ts @@ -0,0 +1,196 @@ +import { Collection } from '#/src/index.js' +import { z } from 'zod' + +const userSchema = z.object({ + id: z.number(), +}) + +const postSchema = z.object({ + title: z.string(), + get author() { + return userSchema.optional() + }, +}) + +it('supports many-to-one relations', async () => { + const users = new Collection({ schema: userSchema }) + const posts = new Collection({ schema: postSchema }) + + posts.defineRelations(({ one }) => ({ + author: one(users), + })) + + const user = await users.create({ id: 1 }) + + const firstPost = await posts.create({ title: 'First', author: user }) + const secondPost = await posts.create({ title: 'Second', author: user }) + + expect.soft(firstPost.author).toEqual(user) + expect.soft(secondPost.author).toEqual(user) + + expect + .soft(posts.findFirst((q) => q.where({ title: 'First' }))) + .toEqual({ title: 'First', author: { id: 1 } }) + expect + .soft(posts.findFirst((q) => q.where({ title: 'Second' }))) + .toEqual({ title: 'Second', author: { id: 1 } }) +}) + +it('scopes a many-to-one relation update to the targeted record', async () => { + const users = new Collection({ schema: userSchema }) + const posts = new Collection({ schema: postSchema }) + + posts.defineRelations(({ one }) => ({ + author: one(users), + })) + + const userOne = await users.create({ id: 1 }) + const userTwo = await users.create({ id: 2 }) + + const firstPost = await posts.create({ title: 'First', author: userOne }) + await posts.create({ title: 'Second', author: userTwo }) + + await posts.update(firstPost, { + data(post) { + post.author = userTwo + }, + }) + await posts.update(firstPost, { + data(post) { + post.author = userOne + }, + }) + + expect(posts.all()).toEqual([ + { title: 'First', author: { id: 1 } }, + { title: 'Second', author: { id: 2 } }, + ]) +}) + +it('updates a many-to-one relation to a foreign record already associated with another owner', async () => { + const languageSchema = z.object({ code: z.string() }) + const userSchema = z.object({ + name: z.string(), + language: languageSchema, + }) + + const languages = new Collection({ schema: languageSchema }) + const users = new Collection({ schema: userSchema }) + + users.defineRelations(({ one }) => ({ + language: one(languages, { unique: false }), + })) + + const langPt = await languages.create({ code: 'pt' }) + const langEn = await languages.create({ code: 'en' }) + + const userOne = await users.create({ name: 'John', language: langPt }) + await users.create({ name: 'Kate', language: langEn }) + + await users.update(userOne, { + data(user) { + user.language = langEn + }, + }) + + expect(users.all()).toEqual([ + { name: 'John', language: { code: 'en' } }, + { name: 'Kate', language: { code: 'en' } }, + ]) +}) + +it("scopes nested updates to the updated owner's foreign record", async () => { + const countrySchema = z.object({ code: z.string() }) + const userSchema = z.object({ + name: z.string(), + country: countrySchema, + }) + + const countries = new Collection({ schema: countrySchema }) + const users = new Collection({ schema: userSchema }) + + users.defineRelations(({ one }) => ({ + country: one(countries), + })) + + const us = await countries.create({ code: 'us' }) + const ca = await countries.create({ code: 'ca' }) + + const userOne = await users.create({ name: 'John', country: us }) + await users.create({ name: 'Kate', country: ca }) + + await users.update(userOne, { + data(user) { + user.country.code = 'uk' + }, + }) + + expect(countries.all()).toEqual([{ code: 'uk' }, { code: 'ca' }]) +}) + +it('creates a self referencing many-to-one relation', async () => { + const userSchema = z.object({ + id: z.number(), + get children() { + return z.array(userSchema).optional() + }, + get parent() { + return userSchema.optional() + }, + }) + + const users = new Collection({ schema: userSchema }) + + users.defineRelations(({ one, many }) => ({ + children: many(users, { role: 'hierarchy' }), + parent: one(users, { role: 'hierarchy' }), + })) + + const parent = await users.create({ + id: 2, + }) + + const child = await users.create({ + id: 1, + parent, + }) + + expect.soft(parent.children).toEqual([expect.objectContaining({ id: 1 })]) + expect.soft(parent.parent).toBeUndefined() + + expect.soft(child.children).toEqual([]) + expect.soft(child.parent).toEqual(expect.objectContaining({ id: 2 })) +}) + +it('updates a self referencing many-to-one relation', async () => { + const userSchema = z.object({ + id: z.number(), + get children() { + return z.array(userSchema).optional() + }, + get parent() { + return userSchema.optional() + }, + }) + + const users = new Collection({ schema: userSchema }) + + users.defineRelations(({ one, many }) => ({ + children: many(users, { role: 'hierarchy' }), + parent: one(users, { role: 'hierarchy' }), + })) + + const parentOne = await users.create({ id: 1 }) + const parentTwo = await users.create({ id: 2 }) + const child = await users.create({ id: 3, parent: parentOne }) + + await users.update(child, { + data(draft) { + draft.parent = parentTwo + }, + }) + + expect.soft(child.parent).toEqual(expect.objectContaining({ id: 2 })) + expect.soft(parentOne.children).toEqual([]) + expect.soft(parentTwo.children).toEqual([expect.objectContaining({ id: 3 })]) +}) diff --git a/tests/relations/one-to-many.test.ts b/tests/relations/one-to-many.test.ts new file mode 100644 index 00000000..a8b16b06 --- /dev/null +++ b/tests/relations/one-to-many.test.ts @@ -0,0 +1,607 @@ +import { Collection } from '#/src/index.js' +import { z } from 'zod' + +const userSchema = z.object({ + id: z.number(), + get posts() { + return z.array(postSchema) + }, +}) + +const postSchema = z.object({ + title: z.string(), + get author() { + return userSchema.optional() + }, +}) + +it('supports a one-to-many relation', async () => { + const users = new Collection({ schema: userSchema }) + const posts = new Collection({ schema: postSchema }) + + users.defineRelations(({ many }) => ({ + posts: many(posts), + })) + + const firstUser = await users.create({ + id: 1, + posts: [await posts.create({ title: 'First' })], + }) + const secondUser = await users.create({ + id: 2, + posts: [await posts.create({ title: 'Second' })], + }) + + expect.soft(firstUser.posts[0]).toEqual({ title: 'First' }) + expect(secondUser.posts[0]).toEqual({ title: 'Second' }) + + expect + .soft(users.findFirst((q) => q.where({ posts: { title: 'First' } }))) + .toEqual({ + id: 1, + posts: [{ title: 'First' }], + }) + expect + .soft(users.findFirst((q) => q.where({ posts: { title: 'Second' } }))) + .toEqual({ + id: 2, + posts: [{ title: 'Second' }], + }) +}) + +it('supports a two-way one-to-many relation', async () => { + const users = new Collection({ schema: userSchema }) + const posts = new Collection({ schema: postSchema }) + + users.defineRelations(({ many }) => ({ + posts: many(posts), + })) + posts.defineRelations(({ one }) => ({ + author: one(users), + })) + + { + const post = await posts.create({ title: 'First' }) + const user = await users.create({ + id: 1, + posts: [post], + }) + + expect(post.author).toEqual(user) + expect(posts.findFirst((q) => q.where({ title: 'First' }))).toMatchObject({ + title: 'First', + author: { id: 1, posts: expect.any(Array) }, + }) + } + + users.clear() + posts.clear() + + { + const user = await users.create({ id: 1, posts: [] }) + const post = await posts.create({ title: 'First', author: user }) + + expect(user.posts).toEqual([post]) + expect(users.findFirst((q) => q.where({ id: 1 }))).toMatchObject({ + id: 1, + posts: [{ title: 'First', author: expect.any(Object) }], + }) + } +}) + +it('differentiates between ambiguous relations', async () => { + const userSchema = z.object({ + id: z.number(), + get posts() { + return z.array(postSchema).optional() + }, + get reviews() { + return z.array(postSchema).optional() + }, + }) + + const postSchema = z.object({ + title: z.string(), + get author() { + return userSchema.optional() + }, + get reviewer() { + return userSchema.optional() + }, + }) + + const users = new Collection({ schema: userSchema }) + const posts = new Collection({ schema: postSchema }) + + users.defineRelations(({ many }) => ({ + posts: many(posts, { role: 'author' }), + reviews: many(posts, { role: 'reviewer' }), + })) + posts.defineRelations(({ one }) => ({ + author: one(users, { role: 'author' }), + reviewer: one(users, { role: 'reviewer' }), + })) + + const post = await posts.create({ title: 'First' }) + + const firstUser = await users.create({ + id: 1, + posts: [post], + reviews: [], + }) + const secondUser = await users.create({ + id: 2, + posts: [], + reviews: [post], + }) + + expect + .soft(firstUser.posts) + .toEqual([expect.objectContaining({ title: 'First' })]) + expect.soft(firstUser.reviews).toEqual([]) + + expect.soft(secondUser.posts).toEqual([]) + expect + .soft(secondUser.reviews) + .toEqual([expect.objectContaining({ title: 'First' })]) +}) + +it('updates an inverse relation when the referenced record is created', async () => { + const users = new Collection({ schema: userSchema }) + const posts = new Collection({ schema: postSchema }) + + users.defineRelations(({ many }) => ({ + posts: many(posts), + })) + posts.defineRelations(({ one }) => ({ + author: one(users), + })) + + const user = await users.create({ + id: 1, + posts: [await posts.create({ title: 'First' })], + }) + await posts.create({ title: 'Second', author: user }) + + expect(users.findFirst((q) => q.where({ id: 1 }))).toEqual({ + id: 1, + posts: [ + { title: 'First', author: user }, + { title: 'Second', author: user }, + ], + }) +}) + +it('updates a one-to-many relation when the referenced record is updated', async () => { + const users = new Collection({ schema: userSchema }) + const posts = new Collection({ schema: postSchema }) + + users.defineRelations(({ many }) => ({ + posts: many(posts), + })) + posts.defineRelations(({ one }) => ({ + author: one(users), + })) + + const user = await users.create({ + id: 1, + posts: [await posts.create({ title: 'First' })], + }) + + await posts.update((q) => q.where({ title: 'First' }), { + data(post) { + post.title = 'Updated' + }, + }) + + expect(users.findFirst((q) => q.where({ id: 1 }))).toEqual({ + id: 1, + posts: [{ title: 'Updated', author: user }], + }) +}) + +it('updates a one-to-many relation when the relational property is reassigned to a new array', async () => { + const users = new Collection({ schema: userSchema }) + const posts = new Collection({ schema: postSchema }) + + users.defineRelations(({ many }) => ({ + posts: many(posts), + })) + + const firstPost = await posts.create({ title: 'First' }) + const user = await users.create({ id: 1, posts: [firstPost] }) + + const secondPost = await posts.create({ title: 'Second' }) + + const updatedUser = await users.update(user, { + data(draft) { + draft.posts = [...draft.posts, secondPost] + }, + }) + + expect(updatedUser).toEqual({ + id: 1, + posts: [{ title: 'First' }, { title: 'Second' }], + }) + + expect(users.findFirst((q) => q.where({ id: 1 }))).toEqual({ + id: 1, + posts: [{ title: 'First' }, { title: 'Second' }], + }) +}) + +it('updates a one-to-many relation when the relational property is mutated via push', async () => { + const users = new Collection({ schema: userSchema }) + const posts = new Collection({ schema: postSchema }) + + users.defineRelations(({ many }) => ({ + posts: many(posts), + })) + + const firstPost = await posts.create({ title: 'First' }) + const user = await users.create({ id: 1, posts: [firstPost] }) + + const secondPost = await posts.create({ title: 'Second' }) + + const updatedUser = await users.update(user, { + data(draft) { + draft.posts.push(secondPost) + }, + }) + + expect(updatedUser).toEqual({ + id: 1, + posts: [{ title: 'First' }, { title: 'Second' }], + }) + + expect(users.findFirst((q) => q.where({ id: 1 }))).toEqual({ + id: 1, + posts: [{ title: 'First' }, { title: 'Second' }], + }) +}) + +it('updates a one-to-many relation when the referenced record is dissociated', async () => { + const users = new Collection({ schema: userSchema }) + const posts = new Collection({ schema: postSchema }) + + users.defineRelations(({ many }) => ({ + posts: many(posts), + })) + posts.defineRelations(({ one }) => ({ + author: one(users), + })) + + await users.create({ + id: 1, + posts: [await posts.create({ title: 'First' })], + }) + + // Set a new author for the post. + const updatedPost = await posts.update((q) => q.where({ title: 'First' }), { + async data(post) { + post.author = await users.create({ id: 2, posts: [] }) + }, + }) + + expect(updatedPost, 'Returns updated record').toEqual({ + title: 'First', + author: { id: 2, posts: [updatedPost] }, + }) + + expect( + posts.findFirst((q) => q.where({ title: 'First' })), + 'Updates the owner record', + ).toEqual({ + title: 'First', + author: { id: 2, posts: [updatedPost] }, + }) + + expect( + users.findFirst((q) => q.where({ id: 1 })), + 'Updates the foreign record', + ).toEqual({ + id: 1, + posts: [], + }) +}) + +it('supports nullable one-to-many relations', async () => { + const users = new Collection({ + schema: z.object({ + id: z.number(), + posts: postSchema.nullable(), + }), + }) + const posts = new Collection({ schema: postSchema }) + + users.defineRelations(({ many }) => ({ + posts: many(posts), + })) + + const user = await users.create({ + id: 1, + posts: null, + }) + + expect.soft(user.posts).toBeNull() + expect.soft(users.findFirst((q) => q.where({ posts: null }))).toEqual(user) +}) + +it('updates the relation when the owner record is deleted', async () => { + const users = new Collection({ schema: userSchema }) + const posts = new Collection({ schema: postSchema }) + + users.defineRelations(({ many }) => ({ + posts: many(posts), + })) + posts.defineRelations(({ one }) => ({ + author: one(users), + })) + + // Deleting the post, user updates. + { + const post = await posts.create({ title: 'First' }) + const user = await users.create({ + id: 1, + posts: [post], + }) + + posts.delete((q) => q.where({ title: 'First' })) + + expect.soft(user.posts).toEqual([]) + expect.soft(posts.all()).toEqual([]) + expect + .soft(posts.findFirst((q) => q.where({ title: 'First' }))) + .toBeUndefined() + } + + users.clear() + posts.clear() + + // Deleting the user, post updates. + { + const post = await posts.create({ title: 'First' }) + const user = await users.create({ + id: 1, + posts: [post], + }) + + users.delete(user) + + expect.soft(post.author).toBeUndefined() + expect + .soft(posts.findFirst((q) => q.where({ title: 'First' }))) + .toEqual({ title: 'First', author: undefined }) + } +}) + +it('cascades foreign record deletion when the owner record is deleted', async () => { + const users = new Collection({ schema: userSchema }) + const posts = new Collection({ schema: postSchema }) + + users.defineRelations(({ many }) => ({ + posts: many(posts), + })) + posts.defineRelations(({ one }) => ({ + // When the referenced author is deleted, delete all their posts. + author: one(users, { onDelete: 'cascade' }), + })) + + const post = await posts.create({ title: 'First' }) + const user = await users.create({ + id: 1, + posts: [post], + }) + + users.delete(user) + + expect(posts.all()).toEqual([]) + expect(users.all()).toEqual([]) +}) + +it('cascades foreign record deletion when clearing the entire collection', async () => { + const users = new Collection({ schema: userSchema }) + const posts = new Collection({ schema: postSchema }) + + users.defineRelations(({ many }) => ({ + posts: many(posts), + })) + posts.defineRelations(({ one }) => ({ + // When the referenced author is deleted, delete all their posts. + author: one(users, { onDelete: 'cascade' }), + })) + + const post = await posts.create({ title: 'First' }) + const user = await users.create({ + id: 1, + posts: [post], + }) + + users.clear() + + expect(posts.all()).toEqual([]) + expect(users.all()).toEqual([]) +}) + +it('supports unique one-to-many relations', async () => { + const users = new Collection({ schema: userSchema }) + const posts = new Collection({ schema: postSchema }) + + users.defineRelations(({ many }) => ({ + posts: many(posts, { unique: true }), + })) + posts.defineRelations(({ one }) => ({ + author: one(users), + })) + + const post = await posts.create({ title: 'First' }) + const user = await users.create({ id: 1, posts: [post] }) + + expect.soft(user).toEqual({ + id: 1, + posts: [post], + }) + expect.soft(post.author).toEqual(user) + expect + .soft(users.findFirst((q) => q.where({ posts: { title: post.title } }))) + .toEqual(user) + expect + .soft(posts.findFirst((q) => q.where({ author: { id: user.id } }))) + .toEqual(post) + + const updatedPost = await posts.update(post, { + data(post) { + post.title = 'Updated' + }, + }) + + expect.soft(user).toEqual({ + id: 1, + posts: [updatedPost], + }) + expect.soft(post.author).toEqual(user) + expect + .soft( + users.findFirst((q) => q.where({ posts: { title: updatedPost!.title } })), + ) + .toEqual(user) + expect + .soft(posts.findFirst((q) => q.where({ author: { id: user.id } }))) + .toEqual(updatedPost) +}) + +it('errors when creating a unique relation with already associated foreign records', async () => { + // Create a user with an already associated post. + { + const users = new Collection({ schema: userSchema }) + const posts = new Collection({ schema: postSchema }) + + users.defineRelations(({ many }) => ({ + posts: many(posts, { unique: true }), + })) + posts.defineRelations(({ one }) => ({ + author: one(users), + })) + + const post = await posts.create({ title: 'First' }) + await users.create({ id: 1, posts: [post] }) + + await expect(users.create({ id: 2, posts: [post] })).rejects.toThrow( + 'Failed to create a unique relation at "posts": the foreign record is already associated with another owner', + ) + } + + // Create a post with an already associated user. + { + const users = new Collection({ schema: userSchema }) + const posts = new Collection({ schema: postSchema }) + + users.defineRelations(({ many }) => ({ + posts: many(posts), + })) + posts.defineRelations(({ one }) => ({ + author: one(users, { unique: true }), + })) + + const user = await users.create({ id: 1, posts: [] }) + await posts.create({ title: 'First', author: user }) + + await expect( + posts.create({ title: 'Second', author: user }), + ).rejects.toThrow( + 'Failed to create a unique relation at "author": the foreign record is already associated with another owner', + ) + } +}) + +it('scopes a nested one-to-many relation update to the targeted record', async () => { + const users = new Collection({ schema: userSchema }) + const posts = new Collection({ schema: postSchema }) + + users.defineRelations(({ many }) => ({ + posts: many(posts), + })) + + const firstUser = await users.create({ + id: 1, + posts: [await posts.create({ title: 'First' })], + }) + await users.create({ + id: 2, + posts: [await posts.create({ title: 'Second' })], + }) + + await users.update(firstUser, { + data(user) { + user.posts[0]!.title = 'Updated' + }, + }) + + expect(posts.all().map((post) => post.title)).toEqual(['Updated', 'Second']) +}) + +it('creates a self referencing one-to-many relation', async () => { + const userSchema = z.object({ + id: z.number(), + get children() { + return z.array(userSchema).optional() + }, + get parent() { + return userSchema.optional() + }, + }) + + const users = new Collection({ schema: userSchema }) + + users.defineRelations(({ one, many }) => ({ + children: many(users, { role: 'hierarchy' }), + parent: one(users, { role: 'hierarchy' }), + })) + + const child = await users.create({ + id: 1, + }) + const parent = await users.create({ + id: 2, + children: [child], + }) + + expect.soft(parent.children).toEqual([expect.objectContaining({ id: 1 })]) + expect.soft(parent.parent).toBeUndefined() + + expect.soft(child.children).toEqual([]) + expect.soft(child.parent).toEqual(expect.objectContaining({ id: 2 })) +}) + +it('updates a self referencing one-to-many relation', async () => { + const userSchema = z.object({ + id: z.number(), + get children() { + return z.array(userSchema).optional() + }, + get parent() { + return userSchema.optional() + }, + }) + + const users = new Collection({ schema: userSchema }) + + users.defineRelations(({ one, many }) => ({ + children: many(users, { role: 'hierarchy' }), + parent: one(users, { role: 'hierarchy' }), + })) + + const childOne = await users.create({ id: 1 }) + const childTwo = await users.create({ id: 2 }) + const parent = await users.create({ id: 3, children: [childOne] }) + + await users.update(parent, { + data(draft) { + draft.children = [childTwo] + }, + }) + + expect.soft(parent.children).toEqual([expect.objectContaining({ id: 2 })]) + expect.soft(childOne.parent).toBeUndefined() + expect.soft(childTwo.parent).toEqual(expect.objectContaining({ id: 3 })) +}) diff --git a/tests/relations/one-to-one.test.ts b/tests/relations/one-to-one.test.ts new file mode 100644 index 00000000..127cefe3 --- /dev/null +++ b/tests/relations/one-to-one.test.ts @@ -0,0 +1,752 @@ +import { z } from 'zod' +import { Collection, RelationError, RelationErrorCodes } from '#/src/index.js' +import { isRecord } from '#/src/utils.js' + +const countrySchema = z.object({ + code: z.string(), +}) + +const userSchema = z.object({ + id: z.number(), + country: countrySchema, +}) + +it('supports a one-to-one relation', async () => { + const users = new Collection({ schema: userSchema }) + const countries = new Collection({ schema: countrySchema }) + + users.defineRelations(({ one }) => ({ + country: one(countries), + })) + + const user = await users.create({ + id: 1, + country: await countries.create({ code: 'us' }), + }) + + expect.soft(user.country).toEqual({ code: 'us' }) + expect + .soft(users.findFirst((q) => q.where({ country: { code: 'us' } }))) + .toEqual(user) +}) + +it('supports one-to-one relations in array models', async () => { + const lists = new Collection({ + schema: z.array( + z.object({ + id: z.number(), + country: countrySchema, + }), + ), + }) + const countries = new Collection({ schema: countrySchema }) + + lists.defineRelations(({ one }) => ({ + country: one(countries), + })) + + const list = await lists.create([ + { id: 1, country: await countries.create({ code: 'us' }) }, + ]) + + expect.soft(list).toEqual([{ id: 1, country: { code: 'us' } }]) + expect + .soft(lists.findFirst((q) => q.where({ country: { code: 'us' } }))) + .toEqual(list) +}) + +it('updates a one-to-one relation with another record', async () => { + const users = new Collection({ schema: userSchema }) + const countries = new Collection({ schema: countrySchema }) + + users.defineRelations(({ one }) => ({ + country: one(countries), + })) + + await users.create({ + id: 1, + country: await countries.create({ code: 'us' }), + }) + + const updatedUser = await users.update((q) => q.where({ id: 1 }), { + async data(user) { + user.country = await countries.create({ code: 'ca' }) + }, + }) + + expect(updatedUser).toEqual({ id: 1, country: { code: 'ca' } }) + expect(users.findFirst((q) => q.where({ country: { code: 'ca' } }))).toEqual( + updatedUser, + ) + expect( + users.findFirst((q) => q.where({ country: { code: 'us' } })), + ).toBeUndefined() +}) + +it('updates relational value when the referenced record is updated', async () => { + const users = new Collection({ schema: userSchema }) + const countries = new Collection({ schema: countrySchema }) + + users.defineRelations(({ one }) => ({ + country: one(countries), + })) + + await users.create({ + id: 1, + country: await countries.create({ code: 'us' }), + }) + + const updatedCountry = await countries.update( + (q) => q.where({ code: 'us' }), + { + data(country) { + country.code = 'ca' + }, + }, + ) + + expect(updatedCountry).toEqual({ code: 'ca' }) + expect(users.findFirst((q) => q.where({ country: { code: 'ca' } }))).toEqual({ + id: 1, + country: { code: 'ca' }, + }) + expect( + users.findFirst((q) => q.where({ country: { code: 'us' } })), + ).toBeUndefined() +}) + +it('supports nested one-to-one relations', async () => { + const users = new Collection({ + schema: z.object({ + id: z.number(), + address: z.object({ + get country() { + return countrySchema + }, + }), + }), + }) + const countries = new Collection({ schema: countrySchema }) + + users.defineRelations(({ one }) => ({ + address: { + country: one(countries), + }, + })) + + const user = await users.create({ + id: 1, + address: { + country: await countries.create({ code: 'us' }), + }, + }) + + expect.soft(user.address.country).toEqual({ code: 'us' }) + expect + .soft( + users.findFirst((q) => q.where({ address: { country: { code: 'us' } } })), + ) + .toEqual(user) +}) + +it('supports nullable one-to-one relations', async () => { + const users = new Collection({ + schema: z.object({ + id: z.number(), + country: countrySchema.nullable(), + }), + }) + const countries = new Collection({ schema: countrySchema }) + + users.defineRelations(({ one }) => ({ + country: one(countries), + })) + + const user = await users.create({ + id: 1, + country: null, + }) + + const schema = z.object({ + id: z.number(), + country: countrySchema.nullable(), + }) + + expect.soft(user.country).toBeNull() + expect.soft(users.findFirst((q) => q.where({ country: null }))).toEqual(user) +}) + +it('updates the relation when the referenced record is deleted', async () => { + const users = new Collection({ + schema: z.object({ + id: z.number(), + get country() { + return countrySchema.optional() + }, + }), + }) + const countries = new Collection({ schema: countrySchema }) + + users.defineRelations(({ one }) => ({ + country: one(countries), + })) + + const user = await users.create({ + id: 1, + country: await countries.create({ code: 'us' }), + }) + + countries.delete((q) => q.where({ code: 'us' })) + + expect.soft(user.country).toBeUndefined() + expect + .soft(users.findFirst((q) => q.where({ country: { code: 'us' } }))) + .toBeUndefined() +}) + +it('applies relation to records created before the relation is defined', async () => { + const users = new Collection({ + schema: z.object({ + id: z.number(), + get country() { + return countrySchema.optional() + }, + }), + }) + const countries = new Collection({ schema: countrySchema }) + + const user = await users.create({ + id: 1, + country: await countries.create({ code: 'us' }), + }) + + users.defineRelations(({ one }) => ({ + country: one(countries), + })) + + expect.soft(user.country).toEqual({ code: 'us' }) + expect.soft(isRecord(user.country)).toBe(true) + + await countries.update((q) => q.where({ code: 'us' }), { + data(country) { + country.code = 'uk' + }, + }) + + expect(user.country).toEqual({ code: 'uk' }) + expect(users.findFirst((q) => q.where({ country: { code: 'uk' } }))).toEqual({ + id: 1, + country: { code: 'uk' }, + }) +}) + +it('supports creating unique one-way one-to-one relations', async () => { + const userSchema = z.object({ id: z.number() }) + const postSchema = z.object({ + title: z.string(), + author: userSchema, + }) + + const users = new Collection({ schema: userSchema }) + const posts = new Collection({ schema: postSchema }) + + posts.defineRelations(({ one }) => ({ + author: one(users, { unique: true }), + })) + + const user = await users.create({ id: 1 }) + const post = await posts.create({ title: 'First', author: user }) + + expect.soft(post.author).toEqual(user) + expect + .soft(posts.findFirst((q) => q.where({ author: { id: user.id } }))) + .toEqual(post) + + const updatedUser = await users.update(user, { + data(user) { + user.id = 2 + }, + }) + + expect.soft(post.author).toEqual(updatedUser) + expect + .soft(posts.findFirst((q) => q.where({ author: { id: updatedUser!.id } }))) + .toEqual(post) + + const anotherUser = await users.create({ id: 5 }) + await expect( + posts.update(post, { + data(post) { + // This must not error since the provided foreign record (user) + // is not associated with any owners (posts). + post.author = anotherUser + }, + }), + 'Updates the unique relational property', + ).resolves.toEqual({ + title: 'First', + author: anotherUser, + }) +}) + +it('supports updating unique one-way one-to-one relations', async () => { + const userSchema = z.object({ id: z.number() }) + const postSchema = z.object({ + title: z.string(), + author: userSchema, + }) + + const users = new Collection({ schema: userSchema }) + const posts = new Collection({ schema: postSchema }) + + posts.defineRelations(({ one }) => ({ + author: one(users, { unique: true }), + })) + + const user = await users.create({ id: 1 }) + const post = await posts.create({ title: 'First', author: user }) + + expect.soft(post.author).toEqual(user) + expect + .soft(posts.findFirst((q) => q.where({ author: { id: user.id } }))) + .toEqual(post) + + const anotherUser = await users.create({ id: 5 }) + await expect( + posts.update(post, { + data(post) { + // This must not error since the provided foreign record (user) + // is not associated with any owners (posts). + post.author = anotherUser + }, + }), + 'Updates the unique relational property', + ).resolves.toEqual({ + title: 'First', + author: anotherUser, + }) +}) + +it('errors when creating a unique one-way relation referencing a taken foreign record', async () => { + const userSchema = z.object({ id: z.number() }) + const postSchema = z.object({ + title: z.string(), + author: userSchema, + }) + + const users = new Collection({ schema: userSchema }) + const posts = new Collection({ schema: postSchema }) + + posts.defineRelations(({ one }) => ({ + author: one(users, { unique: true }), + })) + + const user = await users.create({ id: 1 }) + await posts.create({ title: 'First', author: user }) + + // Cannot create another post referencing the same `user` as the author. + await expect(posts.create({ title: 'Second', author: user })).rejects.toThrow( + new RelationError( + `Failed to create a unique relation at "author": the foreign record is already associated with another owner`, + RelationErrorCodes.FORBIDDEN_UNIQUE_CREATE, + { + path: ['author'], + ownerCollection: posts, + foreignCollections: [users], + options: { unique: true }, + }, + ), + ) +}) + +it('errors when updating a unique one-way relation referencing a taken foreign record', async () => { + const userSchema = z.object({ id: z.number() }) + const postSchema = z.object({ + title: z.string(), + author: userSchema, + }) + + const users = new Collection({ schema: userSchema }) + const posts = new Collection({ schema: postSchema }) + + posts.defineRelations(({ one }) => ({ + author: one(users, { unique: true }), + })) + + const firstUser = await users.create({ id: 1 }) + const secondUser = await users.create({ id: 2 }) + await posts.create({ title: 'First', author: firstUser }) + await posts.create({ title: 'Second', author: secondUser }) + + await expect( + posts.update((q) => q.where({ title: 'Second' }), { + async data(post) { + post.author = firstUser + }, + }), + ).rejects.toThrow( + new RelationError( + `Failed to update a unique relation at "author": the foreign record is already associated with another owner`, + RelationErrorCodes.FORBIDDEN_UNIQUE_UPDATE, + { + path: ['author'], + ownerCollection: posts, + foreignCollections: [users], + options: { unique: true }, + }, + ), + ) +}) + +it('supports creating unique two-way one-to-one relations', async () => { + const userSchema = z.object({ + id: z.number(), + get favoritePost() { + return postSchema + }, + }) + const postSchema = z.object({ + title: z.string(), + get author() { + return userSchema.optional() + }, + }) + + const users = new Collection({ schema: userSchema }) + const posts = new Collection({ schema: postSchema }) + + users.defineRelations(({ one }) => ({ + favoritePost: one(posts, { unique: true }), + })) + posts.defineRelations(({ one }) => ({ + author: one(users, { unique: true }), + })) + + const user = await users.create({ + id: 1, + favoritePost: await posts.create({ title: 'First' }), + }) + expect(user.favoritePost).toEqual({ title: 'First', author: user }) + expect(posts.findFirst((q) => q.where({ author: { id: 1 } }))).toEqual({ + title: 'First', + author: user, + }) +}) + +it('errors when creating a unique two-way relation referencing a taken foreign record', async () => { + const userSchema = z.object({ + id: z.number(), + get favoritePost() { + return postSchema.optional() + }, + }) + const postSchema = z.object({ + title: z.string(), + get author() { + return userSchema.optional() + }, + }) + + const users = new Collection({ schema: userSchema }) + const posts = new Collection({ schema: postSchema }) + + users.defineRelations(({ one }) => ({ + favoritePost: one(posts, { unique: true }), + })) + posts.defineRelations(({ one }) => ({ + author: one(users, { unique: true }), + })) + + const user = await users.create({ + id: 1, + favoritePost: await posts.create({ title: 'First' }), + }) + + await expect( + users.create({ id: 2, favoritePost: user.favoritePost }), + ).rejects.toThrow( + new RelationError( + `Failed to create a unique relation at "favoritePost": the foreign record is already associated with another owner`, + RelationErrorCodes.FORBIDDEN_UNIQUE_CREATE, + { + path: ['favoritePost'], + ownerCollection: users, + foreignCollections: [posts], + options: { unique: true }, + }, + ), + ) + + await expect(posts.create({ title: 'Second', author: user })).rejects.toThrow( + new RelationError( + `Failed to create a unique relation at "author": the foreign record is already associated with another owner`, + RelationErrorCodes.FORBIDDEN_UNIQUE_CREATE, + { + path: ['author'], + ownerCollection: posts, + foreignCollections: [users], + options: { unique: true }, + }, + ), + ) +}) + +it('errors when updating a unique two-way relation referencing a taken foreign record', async () => { + const userSchema = z.object({ + id: z.number(), + get favoritePost() { + return postSchema + }, + }) + const postSchema = z.object({ + title: z.string(), + get author() { + return userSchema.optional() + }, + }) + + const users = new Collection({ schema: userSchema }) + const posts = new Collection({ schema: postSchema }) + + users.defineRelations(({ one }) => ({ + favoritePost: one(posts, { unique: true }), + })) + posts.defineRelations(({ one }) => ({ + author: one(users, { unique: true }), + })) + + const firstUser = await users.create({ + id: 1, + favoritePost: await posts.create({ title: 'First' }), + }) + const secondUser = await users.create({ + id: 2, + favoritePost: await posts.create({ title: 'Second' }), + }) + + await expect( + users.update(secondUser, { + data(user) { + user.favoritePost = firstUser.favoritePost + }, + }), + ).rejects.toThrow( + new RelationError( + `Failed to update a unique relation at "favoritePost": the foreign record is already associated with another owner`, + RelationErrorCodes.FORBIDDEN_UNIQUE_UPDATE, + { + path: ['favoritePost'], + ownerCollection: users, + foreignCollections: [posts], + options: { unique: true }, + }, + ), + ) + + await expect( + posts.update((q) => q.where({ author: { id: 2 } }), { + data(post) { + post.author = firstUser + }, + }), + ).rejects.toThrow( + new RelationError( + `Failed to update a unique relation at "author": the foreign record is already associated with another owner`, + RelationErrorCodes.FORBIDDEN_UNIQUE_UPDATE, + { + path: ['author'], + ownerCollection: posts, + foreignCollections: [users], + options: { unique: true }, + }, + ), + ) +}) + +it('scopes a one-to-one relation update to the targeted record', async () => { + const users = new Collection({ schema: userSchema }) + const countries = new Collection({ schema: countrySchema }) + + users.defineRelations(({ one }) => ({ + country: one(countries), + })) + + await users.create({ + id: 1, + country: await countries.create({ code: 'us' }), + }) + await users.create({ + id: 2, + country: await countries.create({ code: 'ca' }), + }) + + await users.update((q) => q.where({ id: 1 }), { + async data(user) { + user.country = await countries.create({ code: 'uk' }) + }, + }) + + expect(users.all()).toEqual([ + { id: 1, country: { code: 'uk' } }, + { id: 2, country: { code: 'ca' } }, + ]) +}) + +it('scopes a nested one-to-one relation update to the targeted record', async () => { + const users = new Collection({ schema: userSchema }) + const countries = new Collection({ schema: countrySchema }) + + users.defineRelations(({ one }) => ({ + country: one(countries), + })) + + await users.create({ + id: 1, + country: await countries.create({ code: 'us' }), + }) + await users.create({ + id: 2, + country: await countries.create({ code: 'ca' }), + }) + + await users.update((q) => q.where({ id: 1 }), { + data(user) { + user.country.code = 'uk' + }, + }) + + expect(countries.all()).toEqual([{ code: 'uk' }, { code: 'ca' }]) +}) + +it('removes relation listeners when the owner record is deleted', async () => { + const users = new Collection({ schema: userSchema }) + const countries = new Collection({ schema: countrySchema }) + + users.defineRelations(({ one }) => ({ + country: one(countries), + })) + + const totalListeners = () => + countries.hooks.listenerCount('create') + + countries.hooks.listenerCount('delete') + + users.hooks.listenerCount('update') + + users.hooks.listenerCount('delete') + + const baseline = totalListeners() + + const user = await users.create({ + id: 1, + country: await countries.create({ code: 'us' }), + }) + + expect( + totalListeners(), + 'Attaches relation listeners when the owner record is created', + ).toBeGreaterThan(baseline) + + users.delete(user) + + expect( + totalListeners(), + 'Detaches relation listeners when the owner record is deleted', + ).toBe(baseline) +}) + +it('creates a self referencing one-to-one relation', async () => { + const userSchema = z.object({ + id: z.number(), + get child() { + return userSchema.optional() + }, + get parent() { + return userSchema.optional() + }, + }) + + const users = new Collection({ schema: userSchema }) + + users.defineRelations(({ one }) => ({ + child: one(users, { role: 'hierarchy' }), + parent: one(users, { role: 'hierarchy' }), + })) + + const parent = await users.create({ + id: 2, + }) + + const child = await users.create({ + id: 1, + parent, + }) + + expect.soft(parent.child).toEqual(expect.objectContaining({ id: 1 })) + expect.soft(parent.parent).toBeUndefined() + + expect.soft(child.child).toBeUndefined() + expect.soft(child.parent).toEqual(expect.objectContaining({ id: 2 })) +}) + +it('updates a self referencing one-to-one relation', async () => { + const userSchema = z.object({ + id: z.number(), + get child() { + return userSchema.optional() + }, + get parent() { + return userSchema.optional() + }, + }) + + const users = new Collection({ schema: userSchema }) + + users.defineRelations(({ one }) => ({ + child: one(users, { role: 'hierarchy' }), + parent: one(users, { role: 'hierarchy' }), + })) + + const parentOne = await users.create({ id: 1 }) + const parentTwo = await users.create({ id: 2 }) + const child = await users.create({ id: 3, parent: parentOne }) + + await users.update(child, { + data(draft) { + draft.parent = parentTwo + }, + }) + + expect.soft(child.parent).toEqual(expect.objectContaining({ id: 2 })) + expect.soft(parentOne.child).toBeUndefined() + expect.soft(parentTwo.child).toEqual(expect.objectContaining({ id: 3 })) +}) + +it('resolves a cyclic self referencing one-to-one relation', async () => { + const userSchema = z.object({ + id: z.number(), + get child() { + return userSchema.optional() + }, + get parent() { + return userSchema.optional() + }, + }) + + const users = new Collection({ schema: userSchema }) + + users.defineRelations(({ one }) => ({ + child: one(users, { role: 'hierarchy' }), + parent: one(users, { role: 'hierarchy' }), + })) + + const alice = await users.create({ id: 1 }) + const bob = await users.create({ id: 2, parent: alice }) + + await users.update(alice, { + data(draft) { + draft.parent = bob + }, + }) + + expect.soft(alice.parent).toEqual(expect.objectContaining({ id: 2 })) + expect.soft(bob.parent).toEqual(expect.objectContaining({ id: 1 })) + expect.soft(alice.parent?.parent).toEqual(expect.objectContaining({ id: 1 })) +}) diff --git a/tests/relations/polymorphic.test.ts b/tests/relations/polymorphic.test.ts new file mode 100644 index 00000000..fe985a12 --- /dev/null +++ b/tests/relations/polymorphic.test.ts @@ -0,0 +1,59 @@ +import { Collection } from '#/src/collection.js' +import { z } from 'zod' + +const imageSchema = z.object({ + src: z.string(), + get post() { + return postSchema.optional() + }, +}) + +const videoSchema = z.object({ + url: z.string(), + get post() { + return postSchema.optional() + }, +}) + +const postSchema = z.object({ + title: z.string(), + get attachments() { + return z.array(z.union([imageSchema, videoSchema])) + }, +}) + +it('supports polymorphic relations', async () => { + const posts = new Collection({ schema: postSchema }) + const images = new Collection({ schema: imageSchema }) + const videos = new Collection({ schema: videoSchema }) + + posts.defineRelations(({ many }) => ({ + attachments: many([images, videos]), + })) + images.defineRelations(({ one }) => ({ + post: one(posts), + })) + videos.defineRelations(({ one }) => ({ + post: one(posts), + })) + + const post = await posts.create({ + title: 'First', + attachments: [ + await images.create({ src: 'image1.png' }), + await videos.create({ url: 'video1.mp4' }), + ], + }) + + expect(post.attachments).toEqual([ + { src: 'image1.png', post }, + { url: 'video1.mp4', post }, + ]) + + expect( + images.findFirst((q) => q.where({ post: { title: 'First' } })), + ).toEqual({ src: 'image1.png', post }) + expect( + videos.findFirst((q) => q.where({ post: { title: 'First' } })), + ).toEqual({ url: 'video1.mp4', post }) +}) diff --git a/tests/sort.test.ts b/tests/sort.test.ts new file mode 100644 index 00000000..a90fb62d --- /dev/null +++ b/tests/sort.test.ts @@ -0,0 +1,209 @@ +import { Collection } from '#/src/collection.js' +import { z } from 'zod' + +const schema = z.object({ + id: z.number(), + name: z.string(), +}) + +it('sorts the results by a single key (asc)', async () => { + const users = new Collection({ schema }) + + await users.create({ id: 1, name: 'John' }) + await users.create({ id: 2, name: 'Alice' }) + await users.create({ id: 3, name: 'Bob' }) + + expect( + users.findMany(undefined, { + orderBy: { name: 'asc' }, + }), + ).toEqual([ + { id: 2, name: 'Alice' }, + { id: 3, name: 'Bob' }, + { id: 1, name: 'John' }, + ]) + + expect( + users.findMany((q) => q.where({ name: (name) => name.includes('o') }), { + orderBy: { name: 'asc' }, + }), + ).toEqual([ + { id: 3, name: 'Bob' }, + { id: 1, name: 'John' }, + ]) +}) + +it('sorts the results by a single key (desc)', async () => { + const users = new Collection({ schema }) + + await users.create({ id: 1, name: 'John' }) + await users.create({ id: 2, name: 'Alice' }) + await users.create({ id: 3, name: 'Bob' }) + + expect( + users.findMany(undefined, { + orderBy: { name: 'desc' }, + }), + ).toEqual([ + { id: 1, name: 'John' }, + { id: 3, name: 'Bob' }, + { id: 2, name: 'Alice' }, + ]) + + expect( + users.findMany((q) => q.where({ name: (name) => name.includes('o') }), { + orderBy: { name: 'desc' }, + }), + ).toEqual([ + { id: 1, name: 'John' }, + { id: 3, name: 'Bob' }, + ]) +}) + +it('sorts the results by multiple keys (mixed)', async () => { + const users = new Collection({ schema }) + await users.create({ id: 1, name: 'John' }) + await users.create({ id: 2, name: 'Alice' }) + await users.create({ id: 3, name: 'Bob' }) + await users.create({ id: 4, name: 'Bob' }) + + expect( + users.findMany(undefined, { + orderBy: { name: 'asc', id: 'desc' }, + }), + ).toEqual([ + { id: 2, name: 'Alice' }, + { id: 4, name: 'Bob' }, + { id: 3, name: 'Bob' }, + { id: 1, name: 'John' }, + ]) + + expect( + users.findMany((q) => q.where({ name: (name) => name.includes('o') }), { + orderBy: { name: 'asc', id: 'desc' }, + }), + ).toEqual([ + { id: 4, name: 'Bob' }, + { id: 3, name: 'Bob' }, + { id: 1, name: 'John' }, + ]) +}) + +it('sorts the results by a nested key', async () => { + const users = new Collection({ + schema: schema.extend({ + address: z.object({ + street: z.string(), + }), + }), + }) + + await users.create({ id: 1, name: 'John', address: { street: 'C' } }) + await users.create({ id: 2, name: 'Alice', address: { street: 'A' } }) + await users.create({ id: 3, name: 'Bob', address: { street: 'B' } }) + + expect( + users.findMany(undefined, { + orderBy: { address: { street: 'asc' } }, + }), + ).toEqual([ + { id: 2, name: 'Alice', address: { street: 'A' } }, + { id: 3, name: 'Bob', address: { street: 'B' } }, + { id: 1, name: 'John', address: { street: 'C' } }, + ]) + + expect( + users.findMany((q) => q.where({ name: (name) => name.includes('o') }), { + orderBy: { address: { street: 'asc' } }, + }), + ).toEqual([ + { id: 3, name: 'Bob', address: { street: 'B' } }, + { id: 1, name: 'John', address: { street: 'C' } }, + ]) +}) + +it('sorts the results by a list of sort criteria', async () => { + const schema = z.object({ + id: z.number(), + name: z.string(), + age: z.number(), + }) + + const users = new Collection({ schema }) + + await users.create({ id: 1, name: 'John', age: 32 }) + await users.create({ id: 2, name: 'Alice', age: 24 }) + await users.create({ id: 3, name: 'Bob', age: 41 }) + await users.create({ id: 4, name: 'Alice', age: 41 }) + + expect( + users.findMany(undefined, { + orderBy: [{ age: 'asc' }, { name: 'desc' }], + }), + ).toEqual([ + { id: 2, name: 'Alice', age: 24 }, + { id: 1, name: 'John', age: 32 }, + { id: 3, name: 'Bob', age: 41 }, + { id: 4, name: 'Alice', age: 41 }, + ]) +}) + +it('sorts by a relational property', async () => { + const userSchema = z.object({ + id: z.number(), + name: z.string(), + get posts() { + return z.array(postSchema) + }, + }) + const postSchema = z.object({ + id: z.number(), + title: z.string(), + get author() { + return userSchema.optional() + }, + }) + + const users = new Collection({ schema: userSchema }) + const posts = new Collection({ schema: postSchema }) + + users.defineRelations(({ many }) => ({ + posts: many(posts), + })) + posts.defineRelations(({ one }) => ({ + author: one(users, { unique: true }), + })) + + const john = await users.create({ + id: 1, + name: 'John', + posts: await posts.createMany(2, (index) => ({ + id: index + 1, + title: `Post ${index + 1}`, + })), + }) + + const alice = await users.create({ + id: 2, + name: 'Alice', + posts: await posts.createMany(2, (index) => ({ + id: index + 3, + title: `Post ${index + 3}`, + })), + }) + + expect( + posts.findMany(undefined, { + orderBy: { + author: { + name: 'asc', + }, + }, + }), + ).toEqual([ + { id: 3, title: 'Post 3', author: alice }, + { id: 4, title: 'Post 4', author: alice }, + { id: 1, title: 'Post 1', author: john }, + { id: 2, title: 'Post 2', author: john }, + ]) +}) diff --git a/tests/types/collection.test-d.ts b/tests/types/collection.test-d.ts new file mode 100644 index 00000000..21c5b699 --- /dev/null +++ b/tests/types/collection.test-d.ts @@ -0,0 +1,8 @@ +import type { StandardSchemaV1 } from '@standard-schema/spec' +import { Collection, type CollectionOptions } from '#/src/collection.js' + +it('annotates Collection constructor parameters', () => { + expectTypeOf(Collection).constructorParameters.toEqualTypeOf< + [CollectionOptions] + >() +}) diff --git a/tests/types/create-many.test-d.ts b/tests/types/create-many.test-d.ts new file mode 100644 index 00000000..47682158 --- /dev/null +++ b/tests/types/create-many.test-d.ts @@ -0,0 +1,14 @@ +import { Collection, type RecordType } from '#/src/collection.js' +import { z } from 'zod' + +it('infers the initial value factory and return types from the schema', () => { + const users = new Collection({ schema: z.object({ id: z.number() }) }) + + expectTypeOf(users.createMany).parameter(0).toBeNumber() + expectTypeOf(users.createMany) + .parameter(1) + .toEqualTypeOf<(index: number) => { id: number }>() + expectTypeOf(users.createMany).returns.resolves.toEqualTypeOf< + Array> + >() +}) diff --git a/tests/types/create.test-d.ts b/tests/types/create.test-d.ts new file mode 100644 index 00000000..1272bdb8 --- /dev/null +++ b/tests/types/create.test-d.ts @@ -0,0 +1,100 @@ +import { Collection, type RecordType } from '#/src/collection.js' +import { z } from 'zod' + +it('infers initial values from primitives in the schema', async () => { + expectTypeOf(new Collection({ schema: z.object({ id: z.number() }) }).create) + .parameter(0) + .toEqualTypeOf<{ id: number }>() + + expectTypeOf( + new Collection({ schema: z.object({ id: z.number().optional() }) }).create, + ) + .parameter(0) + .toEqualTypeOf<{ id?: number | undefined }>() + + expectTypeOf(new Collection({ schema: z.object({ id: z.string() }) }).create) + .parameter(0) + .toEqualTypeOf<{ id: string }>() + + expectTypeOf( + new Collection({ schema: z.object({ id: z.string().optional() }) }).create, + ) + .parameter(0) + .toEqualTypeOf<{ id?: string | undefined }>() +}) + +it('infers initial values from a nested schema', async () => { + expectTypeOf( + new Collection({ + schema: z.object({ address: z.object({ street: z.string() }) }), + }).create, + ) + .parameter(0) + .toEqualTypeOf<{ address: { street: string } }>() + + expectTypeOf( + new Collection({ + schema: z.object({ + address: z.object({ street: z.string().optional() }), + }), + }).create, + ) + .parameter(0) + .toEqualTypeOf<{ address: { street?: string | undefined } }>() + + expectTypeOf( + new Collection({ + schema: z.object({ + address: z.object({ street: z.string() }).optional(), + }), + }).create, + ) + .parameter(0) + .toEqualTypeOf<{ address?: { street: string } | undefined }>() +}) + +it('infers the record type (return type) from the schema', async () => { + expectTypeOf( + new Collection({ schema: z.object({ id: z.number() }) }).create, + ).returns.resolves.toEqualTypeOf>() + + expectTypeOf( + new Collection({ schema: z.object({ id: z.number().optional() }) }).create, + ).returns.resolves.toEqualTypeOf>() + + expectTypeOf( + new Collection({ schema: z.object({ id: z.string() }) }).create, + ).returns.resolves.toEqualTypeOf>() + + expectTypeOf( + new Collection({ schema: z.object({ id: z.string().optional() }) }).create, + ).returns.resolves.toEqualTypeOf>() + + expectTypeOf( + new Collection({ + schema: z.object({ address: z.object({ street: z.string() }) }), + }).create, + ).returns.resolves.toEqualTypeOf< + RecordType<{ address: { street: string } }> + >() + + expectTypeOf( + new Collection({ + schema: z.object({ + address: z.object({ street: z.string().optional() }), + }), + }).create, + ).returns.resolves.toEqualTypeOf< + RecordType<{ address: { street?: string | undefined } }> + >() + + expectTypeOf( + new Collection({ + schema: z.object({ + address: z.object({ street: z.string() }).optional(), + }), + }).create, + ).returns.resolves.toEqualTypeOf< + RecordType<{ address?: { street: string } | undefined }> + >() +}) diff --git a/tests/types/delete-many.test-d.ts b/tests/types/delete-many.test-d.ts new file mode 100644 index 00000000..a09a7404 --- /dev/null +++ b/tests/types/delete-many.test-d.ts @@ -0,0 +1,76 @@ +import { Collection, type RecordType } from '#/src/collection.js' +import type { SortDirection } from '#/src/sort.js' +import { z } from 'zod' + +it('infers return type from the schema', () => { + const users = new Collection({ + schema: z.object({ id: z.number(), name: z.string() }), + }) + + expectTypeOf(() => + users.deleteMany((q) => q.where({ id: 123 })), + ).returns.toEqualTypeOf>>() + + expectTypeOf(() => + users.deleteMany((q) => q.where({ name: 'John' })), + ).returns.toEqualTypeOf>>() + + users.deleteMany((q) => + q.where({ + // @ts-expect-error + id: 'invalid', + }), + ) + users.deleteMany((q) => + q.where({ + // @ts-expect-error + name: 123, + }), + ) + users.deleteMany((q) => + q.where({ + // @ts-expect-error + unknown: true, + }), + ) +}) + +it('supports a strict mode', () => { + const users = new Collection({ + schema: z.object({ id: z.number(), name: z.string() }), + }) + + expectTypeOf(users.deleteMany) + .parameter(1) + .exclude() + .toHaveProperty('strict') + .toEqualTypeOf() +}) + +it('supports sorting the results', () => { + const users = new Collection({ + schema: z.object({ + id: z.number(), + name: z.string(), + nested: z.object({ key: z.string() }), + }), + }) + + expectTypeOf(users.deleteMany) + .parameter(1) + .exclude() + .toHaveProperty('orderBy') + .toEqualTypeOf< + | { + id?: SortDirection + name?: SortDirection + nested?: { key?: SortDirection } + } + | Array<{ + id?: SortDirection + name?: SortDirection + nested?: { key?: SortDirection } + }> + | undefined + >() +}) diff --git a/tests/types/delete.test-d.ts b/tests/types/delete.test-d.ts new file mode 100644 index 00000000..82cca585 --- /dev/null +++ b/tests/types/delete.test-d.ts @@ -0,0 +1,71 @@ +import { Collection, type RecordType } from '#/src/collection.js' +import { z } from 'zod' + +it('does not require a query argument', () => { + const users = new Collection({ + schema: z.object({ id: z.number(), name: z.string() }), + }) + + expectTypeOf(() => users.delete((q) => q)).returns.toEqualTypeOf< + RecordType<{ id: number; name: string }> | undefined + >() +}) + +it('infers return type from the schema', () => { + const users = new Collection({ + schema: z.object({ id: z.number(), name: z.string() }), + }) + + expectTypeOf(() => + users.delete((q) => q.where({ id: 123 })), + ).returns.toEqualTypeOf< + RecordType<{ id: number; name: string }> | undefined + >() + + expectTypeOf(() => + users.delete((q) => q.where({ name: 'John' })), + ).returns.toEqualTypeOf< + RecordType<{ id: number; name: string }> | undefined + >() + + users.delete((q) => + q.where({ + // @ts-expect-error + id: 'invalid', + }), + ) + users.delete((q) => + q.where({ + // @ts-expect-error + name: 123, + }), + ) + users.delete((q) => + q.where({ + // @ts-expect-error + unknown: true, + }), + ) +}) + +it('annotates the return type as non-nullable if `strict` is set to true', () => { + const users = new Collection({ + schema: z.object({ id: z.number(), name: z.string() }), + }) + + expectTypeOf(() => + users.delete((q) => q.where({ id: 123 }), { strict: true }), + ).returns.toEqualTypeOf>() +}) + +it('supports a strict mode', () => { + const users = new Collection({ + schema: z.object({ id: z.number(), name: z.string() }), + }) + + expectTypeOf(users.delete) + .parameter(1) + .exclude() + .toHaveProperty('strict') + .toEqualTypeOf() +}) diff --git a/tests/types/find-first.test-d.ts b/tests/types/find-first.test-d.ts new file mode 100644 index 00000000..fa090c4c --- /dev/null +++ b/tests/types/find-first.test-d.ts @@ -0,0 +1,85 @@ +import { z } from 'zod' +import { Collection, type RecordType } from '#/src/collection.js' + +it('does not require a query argument', () => { + const users = new Collection({ + schema: z.object({ id: z.number(), name: z.string() }), + }) + + expectTypeOf(() => users.findFirst()).returns.toEqualTypeOf< + RecordType<{ id: number; name: string }> | undefined + >() +}) + +it('infers return type from the schema', () => { + const users = new Collection({ + schema: z.object({ id: z.number(), name: z.string() }), + }) + + expectTypeOf(() => + users.findFirst((q) => q.where({ id: 123 })), + ).returns.toEqualTypeOf< + RecordType<{ id: number; name: string }> | undefined + >() + + expectTypeOf(() => + users.findFirst((q) => q.where({ name: 'John' })), + ).returns.toEqualTypeOf< + RecordType<{ id: number; name: string }> | undefined + >() + + users.findFirst((q) => + q.where({ + // @ts-expect-error + id: 'invalid', + }), + ) + users.findFirst((q) => + q.where({ + // @ts-expect-error + name: 123, + }), + ) + users.findFirst((q) => + q.where({ + // @ts-expect-error + unknown: true, + }), + ) +}) + +it('annotates the return type as non-nullable if `strict` is set to true', () => { + const users = new Collection({ + schema: z.object({ id: z.number(), name: z.string() }), + }) + + expectTypeOf(() => + users.findFirst((q) => q.where({ id: 123 }), { strict: true }), + ).returns.toEqualTypeOf>() +}) + +it('supports a strict mode', () => { + const users = new Collection({ + schema: z.object({ id: z.number(), name: z.string() }), + }) + + expectTypeOf(users.findFirst) + .parameter(1) + .exclude() + .toHaveProperty('strict') + .toEqualTypeOf() +}) + +it('supports top-level record-based predicate via `q.where`', () => { + const users = new Collection({ + schema: z.object({ id: z.number(), name: z.string() }), + }) + + users.findFirst((q) => + q.where((user) => { + expectTypeOf(user).toEqualTypeOf< + RecordType<{ id: number; name: string }> + >() + }), + ) +}) diff --git a/tests/types/find-many.test-d.ts b/tests/types/find-many.test-d.ts new file mode 100644 index 00000000..d5ad7bdc --- /dev/null +++ b/tests/types/find-many.test-d.ts @@ -0,0 +1,127 @@ +import { Collection, type RecordType } from '#/src/collection.js' +import type { SortDirection } from '#/src/sort.js' +import { z } from 'zod' + +it('does not require a query argument', () => { + const users = new Collection({ + schema: z.object({ id: z.number(), name: z.string() }), + }) + + expectTypeOf(() => users.findMany()).returns.toEqualTypeOf< + Array> + >() +}) + +it('infers return type from the schema', () => { + const users = new Collection({ + schema: z.object({ id: z.number(), name: z.string() }), + }) + + expectTypeOf(() => + users.findMany((q) => q.where({ id: 123 })), + ).returns.toEqualTypeOf>>() + + expectTypeOf(() => + users.findMany((q) => q.where({ name: 'John' })), + ).returns.toEqualTypeOf>>() + + users.findMany((q) => + q.where({ + // @ts-expect-error + id: 'invalid', + }), + ) + users.findMany((q) => + q.where({ + // @ts-expect-error + name: 123, + }), + ) + users.findMany((q) => + q.where({ + // @ts-expect-error + unknown: true, + }), + ) +}) + +it('supports a strict mode', () => { + const users = new Collection({ + schema: z.object({ id: z.number(), name: z.string() }), + }) + + expectTypeOf(users.findMany) + .parameter(1) + .exclude() + .toHaveProperty('strict') + .toEqualTypeOf() +}) + +it('strict mode has no effect on the return type', () => { + const users = new Collection({ + schema: z.object({ id: z.number(), name: z.string() }), + }) + + expectTypeOf(() => + users.findMany((q) => q.where({ id: 123 }), { strict: true }), + ).returns.toEqualTypeOf>>() +}) + +it('supports offset-based pagination', () => { + const users = new Collection({ + schema: z.object({ id: z.number(), name: z.string() }), + }) + + users.findMany(undefined, { take: 5 }) + users.findMany(undefined, { skip: 10 }) + users.findMany(undefined, { take: 5, skip: 10 }) + users.findMany(undefined, { skip: 5, cursor: undefined }) + + users.findMany(undefined, { + skip: 5, + cursor: users.findFirst(), + }) +}) + +it('supports cursor-based pagination', () => { + const users = new Collection({ + schema: z.object({ id: z.number(), name: z.string() }), + }) + const cursor = users.findFirst() + + users.findMany(undefined, { cursor }) + users.findMany(undefined, { take: 5, cursor }) + + users.findMany(undefined, { + cursor: users.findFirst(), + skip: 5, + }) +}) + +it('supports sorting the results', () => { + const users = new Collection({ + schema: z.object({ + id: z.number(), + name: z.string(), + nested: z.object({ key: z.string() }), + }), + }) + + expectTypeOf(users.findMany) + .parameter(1) + .exclude() + .toHaveProperty('orderBy') + .toEqualTypeOf< + | { + id?: SortDirection | undefined + name?: SortDirection | undefined + nested?: { key?: SortDirection | undefined } + } + | Array<{ + id?: SortDirection | undefined + name?: SortDirection | undefined + nested?: { key?: SortDirection | undefined } + }> + | undefined + >() +}) diff --git a/tests/types/update-many.test-d.ts b/tests/types/update-many.test-d.ts new file mode 100644 index 00000000..09a864d3 --- /dev/null +++ b/tests/types/update-many.test-d.ts @@ -0,0 +1,119 @@ +import { + Collection, + type RecordType, + type UpdateFunction, +} from '#/src/collection.js' +import type { SortDirection } from '#/src/sort.js' +import { z } from 'zod' + +it('infers return type from the schema', () => { + const users = new Collection({ + schema: z.object({ id: z.number(), name: z.string() }), + }) + + expectTypeOf(() => + users.updateMany((q) => q.where({ id: 123 }), { data: () => {} }), + ).returns.resolves.toEqualTypeOf< + Array> + >() + + expectTypeOf(() => + users.updateMany((q) => q.where({ name: 'John' }), { data: () => {} }), + ).returns.resolves.toEqualTypeOf< + Array> + >() + + users.updateMany( + (q) => + q.where({ + // @ts-expect-error + id: 'invalid', + }), + { data: () => {} }, + ) + users.updateMany( + (q) => + q.where({ + // @ts-expect-error + name: 123, + }), + { data: () => {} }, + ) + users.updateMany( + (q) => + q.where({ + // @ts-expect-error + unknown: true, + }), + { data: () => {} }, + ) +}) + +it('infers update data type from the schema', () => { + const users = new Collection({ + schema: z.object({ + id: z.number(), + name: z.string(), + address: z + .object({ + street: z.string(), + zipCodes: z.array(z.number()), + }) + .optional(), + }), + }) + + expectTypeOf(users.updateMany) + .parameter(1) + .toHaveProperty('data') + .toEqualTypeOf< + UpdateFunction<{ + id: number + name: string + address?: { + street: string + zipCodes: Array + } + }> + >() +}) + +it('supports a `strict` mode', () => { + const users = new Collection({ + schema: z.object({ id: z.number(), name: z.string() }), + }) + + expectTypeOf(users.updateMany) + .parameter(1) + .exclude() + .toHaveProperty('strict') + .toEqualTypeOf() +}) + +it('supports sorting the results', () => { + const users = new Collection({ + schema: z.object({ + id: z.number(), + name: z.string(), + nested: z.object({ key: z.string() }), + }), + }) + + expectTypeOf(users.updateMany) + .parameter(1) + .exclude() + .toHaveProperty('orderBy') + .toEqualTypeOf< + | { + id?: SortDirection + name?: SortDirection + nested?: { key?: SortDirection } + } + | Array<{ + id?: SortDirection + name?: SortDirection + nested?: { key?: SortDirection } + }> + | undefined + >() +}) diff --git a/tests/types/update.test-d.ts b/tests/types/update.test-d.ts new file mode 100644 index 00000000..c39cf01c --- /dev/null +++ b/tests/types/update.test-d.ts @@ -0,0 +1,100 @@ +import { + Collection, + type RecordType, + type UpdateFunction, +} from '#/src/collection.js' +import { z } from 'zod' + +it('infers return type from the schema', () => { + const users = new Collection({ + schema: z.object({ id: z.number(), name: z.string() }), + }) + + expectTypeOf(() => + users.update((q) => q.where({ id: 123 }), { data: () => {} }), + ).returns.resolves.toEqualTypeOf< + RecordType<{ id: number; name: string }> | undefined + >() + + expectTypeOf(() => + users.update((q) => q.where({ name: 'John' }), { data: () => {} }), + ).returns.resolves.toEqualTypeOf< + RecordType<{ id: number; name: string }> | undefined + >() + + users.update( + (q) => + q.where({ + // @ts-expect-error + id: 'invalid', + }), + { data: () => {} }, + ) + users.update( + (q) => + q.where({ + // @ts-expect-error + name: 123, + }), + { data: () => {} }, + ) + users.update( + (q) => + q.where({ + // @ts-expect-error + unknown: true, + }), + { data: () => {} }, + ) +}) + +it('infers update data type from the schema', () => { + const users = new Collection({ + schema: z.object({ + id: z.number(), + name: z.string(), + address: z + .object({ + street: z.string(), + zipCodes: z.array(z.number()), + }) + .optional(), + }), + }) + + expectTypeOf(users.update).parameter(1).toHaveProperty('data').toEqualTypeOf< + UpdateFunction<{ + id: number + name: string + address?: { + street: string + zipCodes: Array + } + }> + >() +}) + +it('supports a `strict` mode', () => { + const users = new Collection({ + schema: z.object({ id: z.number(), name: z.string() }), + }) + + expectTypeOf(users.update) + .parameter(1) + .exclude() + .toHaveProperty('strict') + .toEqualTypeOf() +}) + +it('supports root-level drafts', () => { + const users = new Collection({ + schema: z.object({ id: z.number(), name: z.string() }), + }) + + expectTypeOf(users.update) + .parameter(1) + .exclude() + .toHaveProperty('data') + .extract<(...args: any[]) => any>() + .toEqualTypeOf>() +}) diff --git a/tests/update-many.test.ts b/tests/update-many.test.ts new file mode 100644 index 00000000..d230421c --- /dev/null +++ b/tests/update-many.test.ts @@ -0,0 +1,201 @@ +import { Collection, Query } from '#/src/index.js' +import { z } from 'zod' + +const userSchema = z.object({ + id: z.number(), + name: z.string(), +}) + +it('errors on empty results in a strict mode', async () => { + const users = new Collection({ schema: userSchema }) + + await expect( + users.updateMany((q) => q.where({ id: 123 }), { + data(user) { + user.name = 'Kate' + }, + strict: true, + }), + ).rejects.toThrow( + 'Failed to execute "updateMany" on collection: no records found matching the query', + ) +}) + +it('updates all records matching the query', async () => { + const users = new Collection({ schema: userSchema }) + + await users.create({ id: 1, name: 'John' }) + await users.create({ id: 2, name: 'Alice' }) + await users.create({ id: 3, name: 'Johnatan' }) + + const updatedUsers = await users.updateMany( + (q) => q.where({ name: (name) => name.startsWith('John') }), + { + data(user) { + user.name = user.name.toUpperCase() + }, + }, + ) + + expect.soft(updatedUsers, 'Returns the updated records').toEqual([ + { id: 1, name: 'JOHN' }, + { id: 3, name: 'JOHNATAN' }, + ]) + expect.soft(users.all(), 'Updates records in the store').toEqual([ + { id: 1, name: 'JOHN' }, + { id: 2, name: 'Alice' }, + { id: 3, name: 'JOHNATAN' }, + ]) +}) + +it('supports a query instance as the predicate', async () => { + const users = new Collection({ + schema: z.object({ id: z.number() }), + }) + await users.createMany(5, (index) => ({ id: index + 1 })) + + await expect( + users.updateMany(new Query((user) => user.id % 2 === 0), { + data(user) { + user.id = user.id + 123 + }, + }), + ).resolves.toEqual([{ id: 125 }, { id: 127 }]) +}) + +it('supports a function as the root-level `data` argument', async () => { + const users = new Collection({ schema: userSchema }) + + await users.create({ id: 1, name: 'John' }) + await users.create({ id: 2, name: 'Alice' }) + await users.create({ id: 3, name: 'Johnatan' }) + + const updatedUsers = await users.updateMany( + (q) => q.where({ name: (name) => name.startsWith('John') }), + { + data(user) { + user.name = `${user.name.toUpperCase()}${user.id}` + }, + }, + ) + + expect.soft(updatedUsers, 'Returns the updated records').toEqual([ + { id: 1, name: 'JOHN1' }, + { id: 3, name: 'JOHNATAN3' }, + ]) + expect.soft(users.all(), 'Updates records in the store').toEqual([ + { id: 1, name: 'JOHN1' }, + { id: 2, name: 'Alice' }, + { id: 3, name: 'JOHNATAN3' }, + ]) +}) + +it('supports updating nested arrays', async () => { + const users = new Collection({ + schema: z.object({ + id: z.number(), + numbers: z.array(z.number()), + }), + }) + + await users.create({ id: 1, numbers: [] }) + await users.create({ id: 2, numbers: [] }) + + await expect( + users.updateMany( + (q) => q.where({ numbers: (numbers) => numbers.length === 0 }), + { + data(user) { + user.numbers.push(1) + }, + }, + ), + ).resolves.toEqual([ + { + id: 1, + numbers: [1], + }, + { + id: 2, + numbers: [1], + }, + ]) + expect( + users.findMany((q) => + q.where({ numbers: (numbers) => numbers.includes(1) }), + ), + ).toEqual([ + { + id: 1, + numbers: [1], + }, + { + id: 2, + numbers: [1], + }, + ]) +}) + +it('orders updated records by the given criteria', async () => { + const users = new Collection({ schema: userSchema }) + + await users.create({ id: 1, name: 'John' }) + await users.create({ id: 2, name: 'Alice' }) + await users.create({ id: 3, name: 'Johnatan' }) + + const nextUsers = await users.updateMany( + (q) => q.where({ name: (name) => name.startsWith('John') }), + { + data(user) { + user.name = user.name.toUpperCase() + }, + orderBy: { id: 'desc' }, + }, + ) + + expect.soft(nextUsers).toEqual([ + { id: 3, name: 'JOHNATAN' }, + { id: 1, name: 'JOHN' }, + ]) + expect.soft(users.all(), 'Updates records in the store').toEqual([ + { id: 1, name: 'JOHN' }, + { id: 2, name: 'Alice' }, + { id: 3, name: 'JOHNATAN' }, + ]) +}) + +it('re-applies the schema on updates', async () => { + const users = new Collection({ + schema: userSchema + .extend({ + email: z.email().optional(), + }) + .transform((user) => { + user.email = `${user.name.toLowerCase()}@mail.com` + return user + }), + }) + + await users.create({ id: 1, name: 'John' }) + await users.create({ id: 2, name: 'Alice' }) + await users.create({ id: 3, name: 'Johnatan' }) + + const nextUsers = await users.updateMany( + (q) => q.where({ name: (name) => name.startsWith('John') }), + { + data(user) { + user.name = 'Joey' + }, + }, + ) + + expect.soft(nextUsers, 'Returns the updated records').toEqual([ + { id: 1, name: 'Joey', email: 'joey@mail.com' }, + { id: 3, name: 'Joey', email: 'joey@mail.com' }, + ]) + expect.soft(users.all(), 'Updates records in the store').toEqual([ + { id: 1, name: 'Joey', email: 'joey@mail.com' }, + { id: 2, name: 'Alice', email: 'alice@mail.com' }, + { id: 3, name: 'Joey', email: 'joey@mail.com' }, + ]) +}) diff --git a/tests/update.test.ts b/tests/update.test.ts new file mode 100644 index 00000000..7db6c1de --- /dev/null +++ b/tests/update.test.ts @@ -0,0 +1,213 @@ +import { Collection, Query } from '#/src/index.js' +import { z } from 'zod' + +const schema = z.object({ id: z.number(), name: z.string() }) + +it('returns undefined if updating a non-matching record', async () => { + const users = new Collection({ schema }) + await users.create({ id: 1, name: 'John' }) + + const updatedUser = await users.update((q) => q.where({ name: 'Katelyn' }), { + data(user) { + user.name = 'Kate' + }, + }) + + expect.soft(updatedUser).toBeUndefined() + expect.soft(users.findFirst((q) => q.where({ name: 'Kate' }))).toBeUndefined() +}) + +it('errors on empty results in a strict mode', async () => { + const users = new Collection({ schema }) + + await expect( + users.update((q) => q.where({ id: 123 }), { + data(user) { + user.name = 'Kate' + }, + strict: true, + }), + ).rejects.toThrow( + 'Failed to execute "update" on collection: no record found matching the query', + ) +}) + +it('updates a matching record', async () => { + const users = new Collection({ schema }) + await users.create({ id: 1, name: 'John' }) + + const updatedUser = await users.update((q) => q.where({ name: 'John' }), { + data(user) { + user.name = 'Johnatan' + }, + }) + + expect(updatedUser, 'Returns the updated user').toEqual({ + id: 1, + name: 'Johnatan', + }) + + expect + .soft( + users.findFirst((q) => q.where({ name: 'Johnatan' })), + 'Updates the user in the collection', + ) + .toEqual(updatedUser) + expect.soft(users.findFirst((q) => q.where({ name: 'John' }))).toBeUndefined() +}) + +it('supports a query instance as the predicate', async () => { + const users = new Collection({ + schema: z.object({ id: z.number() }), + }) + await users.createMany(5, (index) => ({ id: index + 1 })) + + await expect( + users.update(new Query((user) => user.id === 3), { + data(user) { + user.id = user.id + 123 + }, + }), + ).resolves.toEqual({ id: 126 }) +}) + +it('supports a function as the root-level `data` argument', async () => { + const users = new Collection({ schema }) + await users.create({ id: 1, name: 'John' }) + + const updatedUser = await users.update((q) => q.where({ name: 'John' }), { + data(user) { + user.name = `${user.name.toUpperCase()}${user.id}` + }, + }) + + expect.soft(updatedUser, 'Returns the updated user').toEqual({ + id: 1, + name: 'JOHN1', + }) + expect + .soft(users.findFirst((q) => q.where({ name: 'JOHN1' }))) + .toEqual(updatedUser) +}) + +it('supports a function as the next value of a nested key', async () => { + const users = new Collection({ + schema: schema.extend({ + address: z.object({ city: z.string() }), + }), + }) + await users.create({ id: 1, name: 'John', address: { city: 'New York' } }) + + const updatedUser = await users.update((q) => q.where({ name: 'John' }), { + data(user) { + user.address.city = user.address.city.toUpperCase() + }, + }) + expect.soft(updatedUser, 'Returns the updated user').toEqual({ + id: 1, + name: 'John', + address: { city: 'NEW YORK' }, + }) + expect + .soft(users.findFirst((q) => q.where({ address: { city: 'NEW YORK' } }))) + .toEqual(updatedUser) +}) + +it('supports deleting an item from an array as an update', async () => { + const users = new Collection({ + schema: z.object({ + id: z.number(), + numbers: z.array(z.number()), + }), + }) + await users.create({ id: 1, numbers: [1, 2, 3] }) + + await expect( + users.update((q) => q.where({ id: 1 }), { + data(user) { + user.numbers.splice(user.numbers.indexOf(2), 1) + }, + }), + ).resolves.toEqual({ id: 1, numbers: [1, 3] }) + + expect(users.findFirst((q) => q.where({ id: 1 }))).toEqual({ + id: 1, + numbers: [1, 3], + }) +}) + +it('re-applies the schema on updates', async () => { + const users = new Collection({ + schema: schema + .extend({ + email: z.email().optional(), + }) + .transform((user) => { + user.email = `${user.name.toLowerCase()}@mail.com` + return user + }), + }) + + await users.create({ id: 1, name: 'John' }) + + const updatedUser = await users.update((q) => q.where({ id: 1 }), { + data(user) { + user.name = 'Johnatan' + }, + }) + + expect(updatedUser).toEqual({ + id: 1, + name: 'Johnatan', + email: 'johnatan@mail.com', + }) + expect( + users.findFirst((q) => q.where({ email: 'johnatan@mail.com' })), + ).toEqual(updatedUser) +}) + +it('updates a root-level array', async () => { + const friends = new Collection({ + schema: z.array(z.object({ name: z.string() })), + }) + + const list = await friends.create([{ name: 'John' }, { name: 'Kate' }]) + + await expect( + friends.update(list, { + data(friends) { + friends.push({ name: 'Alice' }) + }, + }), + ).resolves.toEqual([{ name: 'John' }, { name: 'Kate' }, { name: 'Alice' }]) + expect(friends.all()).toEqual([ + [{ name: 'John' }, { name: 'Kate' }, { name: 'Alice' }], + ]) + + await expect( + friends.update(list, { + data(friends) { + friends.splice(1, 1) + }, + }), + ).resolves.toEqual([{ name: 'John' }, { name: 'Alice' }]) + expect(friends.all()).toEqual([[{ name: 'John' }, { name: 'Alice' }]]) +}) + +it('updates a particular item in a nested array', async () => { + const users = new Collection({ + schema: z.object({ + id: z.number(), + numbers: z.array(z.number()), + }), + }) + const user = await users.create({ id: 1, numbers: [1, 2, 3] }) + + await expect( + users.update(user, { + data(user) { + user.numbers[1] = 500 + }, + }), + ).resolves.toEqual({ id: 1, numbers: [1, 500, 3] }) +}) diff --git a/tsconfig.base.json b/tsconfig.base.json new file mode 100644 index 00000000..c57117c3 --- /dev/null +++ b/tsconfig.base.json @@ -0,0 +1,14 @@ +{ + "exclude": ["node_modules", "build"], + "compilerOptions": { + "strict": true, + "skipLibCheck": true, + "moduleResolution": "nodenext", + "verbatimModuleSyntax": true, + "noUncheckedIndexedAccess": true, + "baseUrl": ".", + "paths": { + "#/src/*": ["src/*"] + } + } +} diff --git a/tsconfig.json b/tsconfig.json index d1a9877d..0fba937f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,18 +1,10 @@ { - "compilerOptions": { - "strict": true, - "strictNullChecks": true, - "outDir": "lib", - "declaration": true, - "moduleResolution": "node", - "downlevelIteration": true, - "esModuleInterop": true, - "noImplicitAny": true, - "baseUrl": ".", - "paths": { - "@mswjs/data": ["./lib"] + "references": [ + { + "path": "./tsconfig.src.json" + }, + { + "path": "./tsconfig.test.json" } - }, - "include": ["src/**/*.ts"], - "exclude": ["node_modules"] + ] } diff --git a/tsconfig.src.json b/tsconfig.src.json new file mode 100644 index 00000000..ec247acc --- /dev/null +++ b/tsconfig.src.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.base.json", + "include": ["./src"], + "compilerOptions": { + "composite": true, + "target": "esnext", + "module": "nodenext", + "moduleResolution": "nodenext" + } +} diff --git a/tsconfig.test.json b/tsconfig.test.json new file mode 100644 index 00000000..d3187f1a --- /dev/null +++ b/tsconfig.test.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.base.json", + "includes": ["./tests/**/*.test.ts"], + "compilerOptions": { + "composite": true, + "target": "esnext", + "module": "nodenext", + "moduleResolution": "nodenext", + "types": ["node", "vitest/globals"], + }, +} diff --git a/tsdown.config.ts b/tsdown.config.ts new file mode 100644 index 00000000..04394255 --- /dev/null +++ b/tsdown.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'tsdown' + +export default defineConfig({ + entry: ['./src/**/*.ts'], + format: 'esm', + outDir: './build', + dts: { + tsconfig: './tsconfig.src.json', + }, +}) diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 00000000..14473d55 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,12 @@ +import { defineConfig, defaultExclude } from 'vitest/config' + +export default defineConfig({ + test: { + globals: true, + exclude: [...defaultExclude, '**/*.browser.test.ts'], + typecheck: { + enabled: true, + tsconfig: './tsconfig.test.json', + }, + }, +}) diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index 27be3bd2..00000000 --- a/yarn.lock +++ /dev/null @@ -1,6373 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.14.5.tgz#23b08d740e83f49c5e59945fbf1b43e80bbf4edb" - integrity sha512-9pzDqyc6OLDaqe+zbACgFkb6fKMNG6CObKpnYXChRsvYGyEdc7CA2BaqeOM+vOtCS5ndmJicPJhKAwYRI6UfFw== - dependencies: - "@babel/highlight" "^7.14.5" - -"@babel/compat-data@^7.15.0": - version "7.15.0" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.15.0.tgz#2dbaf8b85334796cafbb0f5793a90a2fc010b176" - integrity sha512-0NqAC1IJE0S0+lL1SWFMxMkz1pKCNCjI4tr2Zx4LJSXxCLAdr6KyArnY+sno5m3yH9g737ygOyPABDsnXkpxiA== - -"@babel/core@^7.1.0", "@babel/core@^7.7.5": - version "7.15.5" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.15.5.tgz#f8ed9ace730722544609f90c9bb49162dc3bf5b9" - integrity sha512-pYgXxiwAgQpgM1bNkZsDEq85f0ggXMA5L7c+o3tskGMh2BunCI9QUwB9Z4jpvXUOuMdyGKiGKQiRe11VS6Jzvg== - dependencies: - "@babel/code-frame" "^7.14.5" - "@babel/generator" "^7.15.4" - "@babel/helper-compilation-targets" "^7.15.4" - "@babel/helper-module-transforms" "^7.15.4" - "@babel/helpers" "^7.15.4" - "@babel/parser" "^7.15.5" - "@babel/template" "^7.15.4" - "@babel/traverse" "^7.15.4" - "@babel/types" "^7.15.4" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.1.2" - semver "^6.3.0" - source-map "^0.5.0" - -"@babel/generator@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.15.4.tgz#85acb159a267ca6324f9793986991ee2022a05b0" - integrity sha512-d3itta0tu+UayjEORPNz6e1T3FtvWlP5N4V5M+lhp/CxT4oAA7/NcScnpRyspUMLK6tu9MNHmQHxRykuN2R7hw== - dependencies: - "@babel/types" "^7.15.4" - jsesc "^2.5.1" - source-map "^0.5.0" - -"@babel/helper-compilation-targets@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.15.4.tgz#cf6d94f30fbefc139123e27dd6b02f65aeedb7b9" - integrity sha512-rMWPCirulnPSe4d+gwdWXLfAXTTBj8M3guAf5xFQJ0nvFY7tfNAFnWdqaHegHlgDZOCT4qvhF3BYlSJag8yhqQ== - dependencies: - "@babel/compat-data" "^7.15.0" - "@babel/helper-validator-option" "^7.14.5" - browserslist "^4.16.6" - semver "^6.3.0" - -"@babel/helper-function-name@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.15.4.tgz#845744dafc4381a4a5fb6afa6c3d36f98a787ebc" - integrity sha512-Z91cOMM4DseLIGOnog+Z8OI6YseR9bua+HpvLAQ2XayUGU+neTtX+97caALaLdyu53I/fjhbeCnWnRH1O3jFOw== - dependencies: - "@babel/helper-get-function-arity" "^7.15.4" - "@babel/template" "^7.15.4" - "@babel/types" "^7.15.4" - -"@babel/helper-get-function-arity@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.15.4.tgz#098818934a137fce78b536a3e015864be1e2879b" - integrity sha512-1/AlxSF92CmGZzHnC515hm4SirTxtpDnLEJ0UyEMgTMZN+6bxXKg04dKhiRx5Enel+SUA1G1t5Ed/yQia0efrA== - dependencies: - "@babel/types" "^7.15.4" - -"@babel/helper-hoist-variables@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.15.4.tgz#09993a3259c0e918f99d104261dfdfc033f178df" - integrity sha512-VTy085egb3jUGVK9ycIxQiPbquesq0HUQ+tPO0uv5mPEBZipk+5FkRKiWq5apuyTE9FUrjENB0rCf8y+n+UuhA== - dependencies: - "@babel/types" "^7.15.4" - -"@babel/helper-member-expression-to-functions@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.15.4.tgz#bfd34dc9bba9824a4658b0317ec2fd571a51e6ef" - integrity sha512-cokOMkxC/BTyNP1AlY25HuBWM32iCEsLPI4BHDpJCHHm1FU2E7dKWWIXJgQgSFiu4lp8q3bL1BIKwqkSUviqtA== - dependencies: - "@babel/types" "^7.15.4" - -"@babel/helper-module-imports@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.15.4.tgz#e18007d230632dea19b47853b984476e7b4e103f" - integrity sha512-jeAHZbzUwdW/xHgHQ3QmWR4Jg6j15q4w/gCfwZvtqOxoo5DKtLHk8Bsf4c5RZRC7NmLEs+ohkdq8jFefuvIxAA== - dependencies: - "@babel/types" "^7.15.4" - -"@babel/helper-module-transforms@^7.15.4": - version "7.15.7" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.15.7.tgz#7da80c8cbc1f02655d83f8b79d25866afe50d226" - integrity sha512-ZNqjjQG/AuFfekFTY+7nY4RgBSklgTu970c7Rj3m/JOhIu5KPBUuTA9AY6zaKcUvk4g6EbDXdBnhi35FAssdSw== - dependencies: - "@babel/helper-module-imports" "^7.15.4" - "@babel/helper-replace-supers" "^7.15.4" - "@babel/helper-simple-access" "^7.15.4" - "@babel/helper-split-export-declaration" "^7.15.4" - "@babel/helper-validator-identifier" "^7.15.7" - "@babel/template" "^7.15.4" - "@babel/traverse" "^7.15.4" - "@babel/types" "^7.15.6" - -"@babel/helper-optimise-call-expression@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.15.4.tgz#f310a5121a3b9cc52d9ab19122bd729822dee171" - integrity sha512-E/z9rfbAOt1vDW1DR7k4SzhzotVV5+qMciWV6LaG1g4jeFrkDlJedjtV4h0i4Q/ITnUu+Pk08M7fczsB9GXBDw== - dependencies: - "@babel/types" "^7.15.4" - -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.8.0": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9" - integrity sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ== - -"@babel/helper-replace-supers@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.15.4.tgz#52a8ab26ba918c7f6dee28628b07071ac7b7347a" - integrity sha512-/ztT6khaXF37MS47fufrKvIsiQkx1LBRvSJNzRqmbyeZnTwU9qBxXYLaaT/6KaxfKhjs2Wy8kG8ZdsFUuWBjzw== - dependencies: - "@babel/helper-member-expression-to-functions" "^7.15.4" - "@babel/helper-optimise-call-expression" "^7.15.4" - "@babel/traverse" "^7.15.4" - "@babel/types" "^7.15.4" - -"@babel/helper-simple-access@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.15.4.tgz#ac368905abf1de8e9781434b635d8f8674bcc13b" - integrity sha512-UzazrDoIVOZZcTeHHEPYrr1MvTR/K+wgLg6MY6e1CJyaRhbibftF6fR2KU2sFRtI/nERUZR9fBd6aKgBlIBaPg== - dependencies: - "@babel/types" "^7.15.4" - -"@babel/helper-split-export-declaration@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.15.4.tgz#aecab92dcdbef6a10aa3b62ab204b085f776e257" - integrity sha512-HsFqhLDZ08DxCpBdEVtKmywj6PQbwnF6HHybur0MAnkAKnlS6uHkwnmRIkElB2Owpfb4xL4NwDmDLFubueDXsw== - dependencies: - "@babel/types" "^7.15.4" - -"@babel/helper-validator-identifier@^7.14.5", "@babel/helper-validator-identifier@^7.14.9", "@babel/helper-validator-identifier@^7.15.7": - version "7.15.7" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389" - integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w== - -"@babel/helper-validator-option@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz#6e72a1fff18d5dfcb878e1e62f1a021c4b72d5a3" - integrity sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow== - -"@babel/helpers@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.15.4.tgz#5f40f02050a3027121a3cf48d497c05c555eaf43" - integrity sha512-V45u6dqEJ3w2rlryYYXf6i9rQ5YMNu4FLS6ngs8ikblhu2VdR1AqAd6aJjBzmf2Qzh6KOLqKHxEN9+TFbAkAVQ== - dependencies: - "@babel/template" "^7.15.4" - "@babel/traverse" "^7.15.4" - "@babel/types" "^7.15.4" - -"@babel/highlight@^7.14.5": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.5.tgz#6861a52f03966405001f6aa534a01a24d99e8cd9" - integrity sha512-qf9u2WFWVV0MppaL877j2dBtQIDgmidgjGk5VIMw3OadXvYaXn66U1BFlH2t4+t3i+8PhedppRv+i40ABzd+gg== - dependencies: - "@babel/helper-validator-identifier" "^7.14.5" - chalk "^2.0.0" - js-tokens "^4.0.0" - -"@babel/parser@^7.1.0", "@babel/parser@^7.15.4", "@babel/parser@^7.15.5": - version "7.15.7" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.15.7.tgz#0c3ed4a2eb07b165dfa85b3cc45c727334c4edae" - integrity sha512-rycZXvQ+xS9QyIcJ9HXeDWf1uxqlbVFAUq0Rq0dbc50Zb/+wUe/ehyfzGfm9KZZF0kBejYgxltBXocP+gKdL2g== - -"@babel/plugin-syntax-async-generators@^7.8.4": - version "7.8.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" - integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-bigint@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea" - integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-class-properties@^7.8.3": - version "7.12.13" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" - integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== - dependencies: - "@babel/helper-plugin-utils" "^7.12.13" - -"@babel/plugin-syntax-import-meta@^7.8.3": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" - integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-json-strings@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" - integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-logical-assignment-operators@^7.8.3": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" - integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" - integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-numeric-separator@^7.8.3": - version "7.10.4" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" - integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== - dependencies: - "@babel/helper-plugin-utils" "^7.10.4" - -"@babel/plugin-syntax-object-rest-spread@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" - integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-catch-binding@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" - integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-optional-chaining@^7.8.3": - version "7.8.3" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" - integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== - dependencies: - "@babel/helper-plugin-utils" "^7.8.0" - -"@babel/plugin-syntax-top-level-await@^7.8.3": - version "7.14.5" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" - integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== - dependencies: - "@babel/helper-plugin-utils" "^7.14.5" - -"@babel/template@^7.15.4", "@babel/template@^7.3.3": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.15.4.tgz#51898d35dcf3faa670c4ee6afcfd517ee139f194" - integrity sha512-UgBAfEa1oGuYgDIPM2G+aHa4Nlo9Lh6mGD2bDBGMTbYnc38vulXPuC1MGjYILIEmlwl6Rd+BPR9ee3gm20CBtg== - dependencies: - "@babel/code-frame" "^7.14.5" - "@babel/parser" "^7.15.4" - "@babel/types" "^7.15.4" - -"@babel/traverse@^7.1.0", "@babel/traverse@^7.15.4": - version "7.15.4" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.15.4.tgz#ff8510367a144bfbff552d9e18e28f3e2889c22d" - integrity sha512-W6lQD8l4rUbQR/vYgSuCAE75ADyyQvOpFVsvPPdkhf6lATXAsQIG9YdtOcu8BB1dZ0LKu+Zo3c1wEcbKeuhdlA== - dependencies: - "@babel/code-frame" "^7.14.5" - "@babel/generator" "^7.15.4" - "@babel/helper-function-name" "^7.15.4" - "@babel/helper-hoist-variables" "^7.15.4" - "@babel/helper-split-export-declaration" "^7.15.4" - "@babel/parser" "^7.15.4" - "@babel/types" "^7.15.4" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/types@^7.0.0", "@babel/types@^7.15.4", "@babel/types@^7.15.6", "@babel/types@^7.3.0", "@babel/types@^7.3.3": - version "7.15.6" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.15.6.tgz#99abdc48218b2881c058dd0a7ab05b99c9be758f" - integrity sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig== - dependencies: - "@babel/helper-validator-identifier" "^7.14.9" - to-fast-properties "^2.0.0" - -"@bcoe/v8-coverage@^0.2.3": - version "0.2.3" - resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" - integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== - -"@cnakazawa/watch@^1.0.3": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" - integrity sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ== - dependencies: - exec-sh "^0.3.2" - minimist "^1.2.0" - -"@commitlint/cli@^16.0.1": - version "16.0.1" - resolved "https://registry.yarnpkg.com/@commitlint/cli/-/cli-16.0.1.tgz#21905c898ebece7da42277209022b1bc80c4fb39" - integrity sha512-61gGRy65WiVDRsqP0dAR2fAgE3qrTBW3fgz9MySv32y5Ib3ZXXDDq6bGyQqi2dSaPuDYzNCRwwlC7mmQM73T/g== - dependencies: - "@commitlint/format" "^16.0.0" - "@commitlint/lint" "^16.0.0" - "@commitlint/load" "^16.0.0" - "@commitlint/read" "^16.0.0" - "@commitlint/types" "^16.0.0" - lodash "^4.17.19" - resolve-from "5.0.0" - resolve-global "1.0.0" - yargs "^17.0.0" - -"@commitlint/config-conventional@^16.0.0": - version "16.0.0" - resolved "https://registry.yarnpkg.com/@commitlint/config-conventional/-/config-conventional-16.0.0.tgz#f42d9e1959416b5e691c8b5248fc2402adb1fc03" - integrity sha512-mN7J8KlKFn0kROd+q9PB01sfDx/8K/R25yITspL1No8PB4oj9M1p77xWjP80hPydqZG9OvQq+anXK3ZWeR7s3g== - dependencies: - conventional-changelog-conventionalcommits "^4.3.1" - -"@commitlint/config-validator@^16.0.0": - version "16.0.0" - resolved "https://registry.yarnpkg.com/@commitlint/config-validator/-/config-validator-16.0.0.tgz#61dd84895e5dcab6066ff5e21e2b9a96b0ed6323" - integrity sha512-i80DGlo1FeC5jZpuoNV9NIjQN/m2dDV3jYGWg+1Wr+KldptkUHXj+6GY1Akll66lJ3D8s6aUGi3comPLHPtWHg== - dependencies: - "@commitlint/types" "^16.0.0" - ajv "^6.12.6" - -"@commitlint/ensure@^16.0.0": - version "16.0.0" - resolved "https://registry.yarnpkg.com/@commitlint/ensure/-/ensure-16.0.0.tgz#fdac1e60a944a1993deb33b5e8454c559abe9866" - integrity sha512-WdMySU8DCTaq3JPf0tZFCKIUhqxaL54mjduNhu8v4D2AMUVIIQKYMGyvXn94k8begeW6iJkTf9cXBArayskE7Q== - dependencies: - "@commitlint/types" "^16.0.0" - lodash "^4.17.19" - -"@commitlint/execute-rule@^16.0.0": - version "16.0.0" - resolved "https://registry.yarnpkg.com/@commitlint/execute-rule/-/execute-rule-16.0.0.tgz#824e11ba5b208c214a474ae52a51780d32d31ebc" - integrity sha512-8edcCibmBb386x5JTHSPHINwA5L0xPkHQFY8TAuDEt5QyRZY/o5DF8OPHSa5Hx2xJvGaxxuIz4UtAT6IiRDYkw== - -"@commitlint/format@^16.0.0": - version "16.0.0" - resolved "https://registry.yarnpkg.com/@commitlint/format/-/format-16.0.0.tgz#6a6fb2c1e6460aff63cc6eca30a7807a96b0ce73" - integrity sha512-9yp5NCquXL1jVMKL0ZkRwJf/UHdebvCcMvICuZV00NQGYSAL89O398nhqrqxlbjBhM5EZVq0VGcV5+7r3D4zAA== - dependencies: - "@commitlint/types" "^16.0.0" - chalk "^4.0.0" - -"@commitlint/is-ignored@^16.0.0": - version "16.0.0" - resolved "https://registry.yarnpkg.com/@commitlint/is-ignored/-/is-ignored-16.0.0.tgz#5ab4c4a9c7444c1a8540f50a0f1a907dfd78eb70" - integrity sha512-gmAQcwIGC/R/Lp0CEb2b5bfGC7MT5rPe09N8kOGjO/NcdNmfFSZMquwrvNJsq9hnAP0skRdHIsqwlkENkN4Lag== - dependencies: - "@commitlint/types" "^16.0.0" - semver "7.3.5" - -"@commitlint/lint@^16.0.0": - version "16.0.0" - resolved "https://registry.yarnpkg.com/@commitlint/lint/-/lint-16.0.0.tgz#87151a935941073027907fd4752a2e3c83cebbfe" - integrity sha512-HNl15bRC0h+pLzbMzQC3tM0j1aESXsLYhElqKnXcf5mnCBkBkHzu6WwJW8rZbfxX+YwJmNljN62cPhmdBo8x0A== - dependencies: - "@commitlint/is-ignored" "^16.0.0" - "@commitlint/parse" "^16.0.0" - "@commitlint/rules" "^16.0.0" - "@commitlint/types" "^16.0.0" - -"@commitlint/load@>6.1.1", "@commitlint/load@^16.0.0": - version "16.0.0" - resolved "https://registry.yarnpkg.com/@commitlint/load/-/load-16.0.0.tgz#4ab9f8502d0521209ce54d7cce58d419b8c35b48" - integrity sha512-7WhrGCkP6K/XfjBBguLkkI2XUdiiIyMGlNsSoSqgRNiD352EiffhFEApMy1/XOU+viwBBm/On0n5p0NC7e9/4A== - dependencies: - "@commitlint/config-validator" "^16.0.0" - "@commitlint/execute-rule" "^16.0.0" - "@commitlint/resolve-extends" "^16.0.0" - "@commitlint/types" "^16.0.0" - chalk "^4.0.0" - cosmiconfig "^7.0.0" - cosmiconfig-typescript-loader "^1.0.0" - lodash "^4.17.19" - resolve-from "^5.0.0" - typescript "^4.4.3" - -"@commitlint/message@^16.0.0": - version "16.0.0" - resolved "https://registry.yarnpkg.com/@commitlint/message/-/message-16.0.0.tgz#4a467341fc6bc49e5a3ead005dd6aa36fa856b87" - integrity sha512-CmK2074SH1Ws6kFMEKOKH/7hMekGVbOD6vb4alCOo2+33ZSLUIX8iNkDYyrw38Jwg6yWUhLjyQLUxREeV+QIUA== - -"@commitlint/parse@^16.0.0": - version "16.0.0" - resolved "https://registry.yarnpkg.com/@commitlint/parse/-/parse-16.0.0.tgz#5ce05af14edff806effc702ba910fcb32fcb192a" - integrity sha512-F9EjFlMw4MYgBEqoRrWZZKQBzdiJzPBI0qFDFqwUvfQsMmXEREZ242T4R5bFwLINWaALFLHEIa/FXEPa6QxCag== - dependencies: - "@commitlint/types" "^16.0.0" - conventional-changelog-angular "^5.0.11" - conventional-commits-parser "^3.2.2" - -"@commitlint/read@^16.0.0": - version "16.0.0" - resolved "https://registry.yarnpkg.com/@commitlint/read/-/read-16.0.0.tgz#92fab45d4e0e4d7d049427306500270b3e459221" - integrity sha512-H4T2zsfmYQK9B+JtoQaCXWBHUhgIJyOzWZjSfuIV9Ce69/OgHoffNpLZPF2lX6yKuDrS1SQFhI/kUCjVc/e4ew== - dependencies: - "@commitlint/top-level" "^16.0.0" - "@commitlint/types" "^16.0.0" - fs-extra "^10.0.0" - git-raw-commits "^2.0.0" - -"@commitlint/resolve-extends@^16.0.0": - version "16.0.0" - resolved "https://registry.yarnpkg.com/@commitlint/resolve-extends/-/resolve-extends-16.0.0.tgz#2136f01d81bccc29091f2720b42c8c96aa59c56e" - integrity sha512-Z/w9MAQUcxeawpCLtjmkVNXAXOmB2nhW+LYmHEZcx9O6UTauF/1+uuZ2/r0MtzTe1qw2JD+1QHVhEWYHVPlkdA== - dependencies: - "@commitlint/config-validator" "^16.0.0" - "@commitlint/types" "^16.0.0" - import-fresh "^3.0.0" - lodash "^4.17.19" - resolve-from "^5.0.0" - resolve-global "^1.0.0" - -"@commitlint/rules@^16.0.0": - version "16.0.0" - resolved "https://registry.yarnpkg.com/@commitlint/rules/-/rules-16.0.0.tgz#79d28c3678d2d1f7f1cdbedaedb30b01a86ee75b" - integrity sha512-AOl0y2SBTdJ1bvIv8nwHvQKRT/jC1xb09C5VZwzHoT8sE8F54KDeEzPCwHQFgUcWdGLyS10kkOTAH2MyA8EIlg== - dependencies: - "@commitlint/ensure" "^16.0.0" - "@commitlint/message" "^16.0.0" - "@commitlint/to-lines" "^16.0.0" - "@commitlint/types" "^16.0.0" - execa "^5.0.0" - -"@commitlint/to-lines@^16.0.0": - version "16.0.0" - resolved "https://registry.yarnpkg.com/@commitlint/to-lines/-/to-lines-16.0.0.tgz#799980a89072302445baf595e20092fb86f0a58a" - integrity sha512-iN/qU38TCKU7uKOg6RXLpD49wNiuI0TqMqybHbjefUeP/Jmzxa8ishryj0uLyVdrAl1ZjGeD1ukXGMTtvqz8iA== - -"@commitlint/top-level@^16.0.0": - version "16.0.0" - resolved "https://registry.yarnpkg.com/@commitlint/top-level/-/top-level-16.0.0.tgz#7c2efc33cc37df839b3de558c0bc2eaddb64efe6" - integrity sha512-/Jt6NLxyFkpjL5O0jxurZPCHURZAm7cQCqikgPCwqPAH0TLgwqdHjnYipl8J+AGnAMGDip4FNLoYrtgIpZGBYw== - dependencies: - find-up "^5.0.0" - -"@commitlint/types@^16.0.0": - version "16.0.0" - resolved "https://registry.yarnpkg.com/@commitlint/types/-/types-16.0.0.tgz#3c133f106d36132756c464071a7f2290966727a3" - integrity sha512-+0FvYOAS39bJ4aKjnYn/7FD4DfWkmQ6G/06I4F0Gvu4KS5twirEg8mIcLhmeRDOOKn4Tp8PwpLwBiSA6npEMQA== - dependencies: - chalk "^4.0.0" - -"@cspotcode/source-map-consumer@0.8.0": - version "0.8.0" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b" - integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg== - -"@cspotcode/source-map-support@0.7.0": - version "0.7.0" - resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5" - integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA== - dependencies: - "@cspotcode/source-map-consumer" "0.8.0" - -"@istanbuljs/load-nyc-config@^1.0.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" - integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ== - dependencies: - camelcase "^5.3.1" - find-up "^4.1.0" - get-package-type "^0.1.0" - js-yaml "^3.13.1" - resolve-from "^5.0.0" - -"@istanbuljs/schema@^0.1.2": - version "0.1.3" - resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98" - integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA== - -"@jest/console@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/console/-/console-26.6.2.tgz#4e04bc464014358b03ab4937805ee36a0aeb98f2" - integrity sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g== - dependencies: - "@jest/types" "^26.6.2" - "@types/node" "*" - chalk "^4.0.0" - jest-message-util "^26.6.2" - jest-util "^26.6.2" - slash "^3.0.0" - -"@jest/core@^26.6.3": - version "26.6.3" - resolved "https://registry.yarnpkg.com/@jest/core/-/core-26.6.3.tgz#7639fcb3833d748a4656ada54bde193051e45fad" - integrity sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw== - dependencies: - "@jest/console" "^26.6.2" - "@jest/reporters" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - exit "^0.1.2" - graceful-fs "^4.2.4" - jest-changed-files "^26.6.2" - jest-config "^26.6.3" - jest-haste-map "^26.6.2" - jest-message-util "^26.6.2" - jest-regex-util "^26.0.0" - jest-resolve "^26.6.2" - jest-resolve-dependencies "^26.6.3" - jest-runner "^26.6.3" - jest-runtime "^26.6.3" - jest-snapshot "^26.6.2" - jest-util "^26.6.2" - jest-validate "^26.6.2" - jest-watcher "^26.6.2" - micromatch "^4.0.2" - p-each-series "^2.1.0" - rimraf "^3.0.0" - slash "^3.0.0" - strip-ansi "^6.0.0" - -"@jest/environment@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-26.6.2.tgz#ba364cc72e221e79cc8f0a99555bf5d7577cf92c" - integrity sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA== - dependencies: - "@jest/fake-timers" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/node" "*" - jest-mock "^26.6.2" - -"@jest/fake-timers@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-26.6.2.tgz#459c329bcf70cee4af4d7e3f3e67848123535aad" - integrity sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA== - dependencies: - "@jest/types" "^26.6.2" - "@sinonjs/fake-timers" "^6.0.1" - "@types/node" "*" - jest-message-util "^26.6.2" - jest-mock "^26.6.2" - jest-util "^26.6.2" - -"@jest/globals@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-26.6.2.tgz#5b613b78a1aa2655ae908eba638cc96a20df720a" - integrity sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA== - dependencies: - "@jest/environment" "^26.6.2" - "@jest/types" "^26.6.2" - expect "^26.6.2" - -"@jest/reporters@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-26.6.2.tgz#1f518b99637a5f18307bd3ecf9275f6882a667f6" - integrity sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw== - dependencies: - "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" - chalk "^4.0.0" - collect-v8-coverage "^1.0.0" - exit "^0.1.2" - glob "^7.1.2" - graceful-fs "^4.2.4" - istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^4.0.3" - istanbul-lib-report "^3.0.0" - istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.0.2" - jest-haste-map "^26.6.2" - jest-resolve "^26.6.2" - jest-util "^26.6.2" - jest-worker "^26.6.2" - slash "^3.0.0" - source-map "^0.6.0" - string-length "^4.0.1" - terminal-link "^2.0.0" - v8-to-istanbul "^7.0.0" - optionalDependencies: - node-notifier "^8.0.0" - -"@jest/source-map@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-26.6.2.tgz#29af5e1e2e324cafccc936f218309f54ab69d535" - integrity sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA== - dependencies: - callsites "^3.0.0" - graceful-fs "^4.2.4" - source-map "^0.6.0" - -"@jest/test-result@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-26.6.2.tgz#55da58b62df134576cc95476efa5f7949e3f5f18" - integrity sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ== - dependencies: - "@jest/console" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/istanbul-lib-coverage" "^2.0.0" - collect-v8-coverage "^1.0.0" - -"@jest/test-sequencer@^26.6.3": - version "26.6.3" - resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz#98e8a45100863886d074205e8ffdc5a7eb582b17" - integrity sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw== - dependencies: - "@jest/test-result" "^26.6.2" - graceful-fs "^4.2.4" - jest-haste-map "^26.6.2" - jest-runner "^26.6.3" - jest-runtime "^26.6.3" - -"@jest/transform@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-26.6.2.tgz#5ac57c5fa1ad17b2aae83e73e45813894dcf2e4b" - integrity sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA== - dependencies: - "@babel/core" "^7.1.0" - "@jest/types" "^26.6.2" - babel-plugin-istanbul "^6.0.0" - chalk "^4.0.0" - convert-source-map "^1.4.0" - fast-json-stable-stringify "^2.0.0" - graceful-fs "^4.2.4" - jest-haste-map "^26.6.2" - jest-regex-util "^26.0.0" - jest-util "^26.6.2" - micromatch "^4.0.2" - pirates "^4.0.1" - slash "^3.0.0" - source-map "^0.6.1" - write-file-atomic "^3.0.0" - -"@jest/types@^26.6.2": - version "26.6.2" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-26.6.2.tgz#bef5a532030e1d88a2f5a6d933f84e97226ed48e" - integrity sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^3.0.0" - "@types/node" "*" - "@types/yargs" "^15.0.0" - chalk "^4.0.0" - -"@mswjs/cookies@^0.1.6": - version "0.1.6" - resolved "https://registry.yarnpkg.com/@mswjs/cookies/-/cookies-0.1.6.tgz#176f77034ab6d7373ae5c94bcbac36fee8869249" - integrity sha512-A53XD5TOfwhpqAmwKdPtg1dva5wrng2gH5xMvklzbd9WLTSVU953eCRa8rtrrm6G7Cy60BOGsBRN89YQK0mlKA== - dependencies: - "@types/set-cookie-parser" "^2.4.0" - set-cookie-parser "^2.4.6" - -"@mswjs/interceptors@^0.12.6": - version "0.12.7" - resolved "https://registry.yarnpkg.com/@mswjs/interceptors/-/interceptors-0.12.7.tgz#0d1cd4cd31a0f663e0455993951201faa09d0909" - integrity sha512-eGjZ3JRAt0Fzi5FgXiV/P3bJGj0NqsN7vBS0J0FO2AQRQ0jCKQS4lEFm4wvlSgKQNfeuc/Vz6d81VtU3Gkx/zg== - dependencies: - "@open-draft/until" "^1.0.3" - "@xmldom/xmldom" "^0.7.2" - debug "^4.3.2" - headers-utils "^3.0.2" - outvariant "^1.2.0" - strict-event-emitter "^0.2.0" - -"@open-draft/until@^1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@open-draft/until/-/until-1.0.3.tgz#db9cc719191a62e7d9200f6e7bab21c5b848adca" - integrity sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q== - -"@open-draft/until@^2.0.0": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@open-draft/until/-/until-2.0.0.tgz#13d79bb827eb1be21cea4d29cdc60ca3312ec02f" - integrity sha512-0zJhDjNR0aH1d68TiD6GnYr18dcuOiyTx8xV/I7fp9+z/VQ20e305aObW1/DO5/fiCOztscmvJsCjJDYDhFW6w== - -"@ossjs/release@^0.2.1": - version "0.2.1" - resolved "https://registry.yarnpkg.com/@ossjs/release/-/release-0.2.1.tgz#3964e80db67b39e1fc5086f47981851d6962c0a4" - integrity sha512-XlXg5/MwDxtIHz3DlWrowHmM015lP1gQLyQSVLK5e8F+Jsvj4mk8/dZjZO2+hfEb0CvdsZYHBttbCug0x9+JTA== - dependencies: - "@open-draft/until" "^2.0.0" - "@types/conventional-commits-parser" "^3.0.2" - "@types/issue-parser" "^3.0.1" - "@types/node" "^16.11.27" - "@types/node-fetch" "2.x" - "@types/rc" "^1.2.1" - "@types/registry-auth-token" "^4.2.1" - "@types/semver" "^7.3.9" - "@types/yargs" "^17.0.10" - conventional-commits-parser "^3.2.4" - get-stream "^6.0.1" - git-log-parser "^1.2.0" - issue-parser "^6.0.0" - node-fetch "2.6.7" - outvariant "^1.3.0" - pino "^7.10.0" - pino-pretty "^7.6.1" - rc "^1.2.8" - registry-auth-token "^4.2.1" - semver "^7.3.7" - yargs "^17.4.1" - -"@sinonjs/commons@^1.7.0": - version "1.8.3" - resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" - integrity sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ== - dependencies: - type-detect "4.0.8" - -"@sinonjs/fake-timers@^6.0.1": - version "6.0.1" - resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40" - integrity sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA== - dependencies: - "@sinonjs/commons" "^1.7.0" - -"@tootallnate/once@1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" - integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== - -"@tsconfig/node10@^1.0.7": - version "1.0.8" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" - integrity sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg== - -"@tsconfig/node12@^1.0.7": - version "1.0.9" - resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.9.tgz#62c1f6dee2ebd9aead80dc3afa56810e58e1a04c" - integrity sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw== - -"@tsconfig/node14@^1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.1.tgz#95f2d167ffb9b8d2068b0b235302fafd4df711f2" - integrity sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg== - -"@tsconfig/node16@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.2.tgz#423c77877d0569db20e1fc80885ac4118314010e" - integrity sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA== - -"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7": - version "7.1.16" - resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.16.tgz#bc12c74b7d65e82d29876b5d0baf5c625ac58702" - integrity sha512-EAEHtisTMM+KaKwfWdC3oyllIqswlznXCIVCt7/oRNrh+DhgT4UEBNC/jlADNjvw7UnfbcdkGQcPVZ1xYiLcrQ== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - "@types/babel__generator" "*" - "@types/babel__template" "*" - "@types/babel__traverse" "*" - -"@types/babel__generator@*": - version "7.6.3" - resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.3.tgz#f456b4b2ce79137f768aa130d2423d2f0ccfaba5" - integrity sha512-/GWCmzJWqV7diQW54smJZzWbSFf4QYtF71WCKhcx6Ru/tFyQIY2eiiITcCAeuPbNSvT9YCGkVMqqvSk2Z0mXiA== - dependencies: - "@babel/types" "^7.0.0" - -"@types/babel__template@*": - version "7.4.1" - resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969" - integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - -"@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6": - version "7.14.2" - resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.14.2.tgz#ffcd470bbb3f8bf30481678fb5502278ca833a43" - integrity sha512-K2waXdXBi2302XUdcHcR1jCeU0LL4TD9HRs/gk0N2Xvrht+G/BfJa4QObBQZfhMdxiCpV3COl5Nfq4uKTeTnJA== - dependencies: - "@babel/types" "^7.3.0" - -"@types/body-parser@*": - version "1.19.1" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.1.tgz#0c0174c42a7d017b818303d4b5d969cb0b75929c" - integrity sha512-a6bTJ21vFOGIkwM0kzh9Yr89ziVxq4vYH2fQ6N8AeipEzai/cFK6aGMArIkUeIdRIgpwQa+2bXiLuUJCpSf2Cg== - dependencies: - "@types/connect" "*" - "@types/node" "*" - -"@types/connect@*": - version "3.4.35" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" - integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== - dependencies: - "@types/node" "*" - -"@types/conventional-commits-parser@^3.0.2": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/conventional-commits-parser/-/conventional-commits-parser-3.0.2.tgz#144b208c7344838bb045860fe1ddd10d4ae68f7c" - integrity sha512-1kVPUHFaart1iGRFxKn8WNXYEDVAgMb+DLatgql2dGg9jTGf3bNxWtN//C/tDG3ckOLg4u7SSx+qcn8VjzI5zg== - dependencies: - "@types/node" "*" - -"@types/cookie@^0.4.1": - version "0.4.1" - resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d" - integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== - -"@types/debug@^4.1.5": - version "4.1.7" - resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.7.tgz#7cc0ea761509124709b8b2d1090d8f6c17aadb82" - integrity sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg== - dependencies: - "@types/ms" "*" - -"@types/eslint-scope@^3.7.0": - version "3.7.1" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.1.tgz#8dc390a7b4f9dd9f1284629efce982e41612116e" - integrity sha512-SCFeogqiptms4Fg29WpOTk5nHIzfpKCemSN63ksBQYKTcXoJEmJagV+DhVmbapZzY4/5YaOV1nZwrsU79fFm1g== - dependencies: - "@types/eslint" "*" - "@types/estree" "*" - -"@types/eslint@*": - version "7.28.0" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.28.0.tgz#7e41f2481d301c68e14f483fe10b017753ce8d5a" - integrity sha512-07XlgzX0YJUn4iG1ocY4IX9DzKSmMGUs6ESKlxWhZRaa0fatIWaHWUVapcuGa8r5HFnTqzj+4OCjd5f7EZ/i/A== - dependencies: - "@types/estree" "*" - "@types/json-schema" "*" - -"@types/estree@*", "@types/estree@^0.0.50": - version "0.0.50" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83" - integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw== - -"@types/express-serve-static-core@^4.17.18": - version "4.17.24" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.24.tgz#ea41f93bf7e0d59cd5a76665068ed6aab6815c07" - integrity sha512-3UJuW+Qxhzwjq3xhwXm2onQcFHn76frIYVbTu+kn24LFxI+dEhdfISDFovPB8VpEgW8oQCTpRuCe+0zJxB7NEA== - dependencies: - "@types/node" "*" - "@types/qs" "*" - "@types/range-parser" "*" - -"@types/express@^4.17.11": - version "4.17.13" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.13.tgz#a76e2995728999bab51a33fabce1d705a3709034" - integrity sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA== - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "^4.17.18" - "@types/qs" "*" - "@types/serve-static" "*" - -"@types/faker@^5.5.3": - version "5.5.8" - resolved "https://registry.yarnpkg.com/@types/faker/-/faker-5.5.8.tgz#6649adfdfdbb0acf95361fc48f2d0ca6e88bd1cf" - integrity sha512-bsl0rYsaZVHlZkynL5O04q6YXDmVjcid6MbOHWqvtE2WWs/EKhp0qchDDhVWlWyQXUffX1G83X9LnMxRl8S/Mw== - -"@types/graceful-fs@^4.1.2": - version "4.1.5" - resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" - integrity sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw== - dependencies: - "@types/node" "*" - -"@types/inquirer@^7.3.3": - version "7.3.3" - resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-7.3.3.tgz#92e6676efb67fa6925c69a2ee638f67a822952ac" - integrity sha512-HhxyLejTHMfohAuhRun4csWigAMjXTmRyiJTU1Y/I1xmggikFMkOUoMQRlFm+zQcPEGHSs3io/0FAmNZf8EymQ== - dependencies: - "@types/through" "*" - rxjs "^6.4.0" - -"@types/issue-parser@^3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/issue-parser/-/issue-parser-3.0.1.tgz#05240316890ec37fef7cd64d19019ac95733c7a8" - integrity sha512-cdggbeJIxWoIB8CB57BvenONrQZcBuEf2uddxMRNIy2jgdcnSxnY71tQcNrxdqTG4VmQP5fdLLE9E+jCnMK0Fg== - -"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1": - version "2.0.3" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz#4ba8ddb720221f432e443bd5f9117fd22cfd4762" - integrity sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw== - -"@types/istanbul-lib-report@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686" - integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg== - dependencies: - "@types/istanbul-lib-coverage" "*" - -"@types/istanbul-reports@^3.0.0": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff" - integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw== - dependencies: - "@types/istanbul-lib-report" "*" - -"@types/jest@^26.0.22": - version "26.0.24" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-26.0.24.tgz#943d11976b16739185913a1936e0de0c4a7d595a" - integrity sha512-E/X5Vib8BWqZNRlDxj9vYXhsDwPYbPINqKF9BsnSoon4RQ0D9moEuLD8txgyypFLH7J4+Lho9Nr/c8H0Fi+17w== - dependencies: - jest-diff "^26.0.0" - pretty-format "^26.0.0" - -"@types/js-levenshtein@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@types/js-levenshtein/-/js-levenshtein-1.1.0.tgz#9541eec4ad6e3ec5633270a3a2b55d981edc44a9" - integrity sha512-14t0v1ICYRtRVcHASzes0v/O+TIeASb8aD55cWF1PidtInhFWSXcmhzhHqGjUWf9SUq1w70cvd1cWKUULubAfQ== - -"@types/json-schema@*", "@types/json-schema@^7.0.8": - version "7.0.9" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" - integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== - -"@types/lodash@^4.14.172": - version "4.14.175" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.175.tgz#b78dfa959192b01fae0ad90e166478769b215f45" - integrity sha512-XmdEOrKQ8a1Y/yxQFOMbC47G/V2VDO1GvMRnl4O75M4GW/abC5tnfzadQYkqEveqRM1dEJGFFegfPNA2vvx2iw== - -"@types/md5@^2.3.0": - version "2.3.1" - resolved "https://registry.yarnpkg.com/@types/md5/-/md5-2.3.1.tgz#010bcf3bb50a2cff3a574cb1c0b4051a9c67d6bc" - integrity sha512-OK3oe+ALIoPSo262lnhAYwpqFNXbiwH2a+0+Z5YBnkQEwWD8fk5+PIeRhYA48PzvX9I4SGNpWy+9bLj8qz92RQ== - dependencies: - "@types/node" "*" - -"@types/mime@^1": - version "1.3.2" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" - integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== - -"@types/minimist@*", "@types/minimist@^1.2.0": - version "1.2.2" - resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.2.tgz#ee771e2ba4b3dc5b372935d549fd9617bf345b8c" - integrity sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ== - -"@types/ms@*": - version "0.7.31" - resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197" - integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA== - -"@types/mustache@^4.1.1": - version "4.1.2" - resolved "https://registry.yarnpkg.com/@types/mustache/-/mustache-4.1.2.tgz#d0e158013c81674a5b6d8780bc3fe234e1804eaf" - integrity sha512-c4OVMMcyodKQ9dpwBwh3ofK9P6U9ZktKU9S+p33UqwMNN1vlv2P0zJZUScTshnx7OEoIIRcCFNQ904sYxZz8kg== - -"@types/node-fetch@2.x": - version "2.6.1" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.1.tgz#8f127c50481db65886800ef496f20bbf15518975" - integrity sha512-oMqjURCaxoSIsHSr1E47QHzbmzNR5rK8McHuNb11BOM9cHcIK3Avy0s/b2JlXHoQGTYS3NsvWzV1M0iK7l0wbA== - dependencies: - "@types/node" "*" - form-data "^3.0.0" - -"@types/node-fetch@^2.5.10": - version "2.5.12" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.12.tgz#8a6f779b1d4e60b7a57fb6fd48d84fb545b9cc66" - integrity sha512-MKgC4dlq4kKNa/mYrwpKfzQMB5X3ee5U6fSprkKpToBqBmX4nFZL9cW5jl6sWn+xpRJ7ypWh2yyqqr8UUCstSw== - dependencies: - "@types/node" "*" - form-data "^3.0.0" - -"@types/node@*": - version "16.10.3" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.10.3.tgz#7a8f2838603ea314d1d22bb3171d899e15c57bd5" - integrity sha512-ho3Ruq+fFnBrZhUYI46n/bV2GjwzSkwuT4dTf0GkuNFmnb8nq4ny2z9JEVemFi6bdEJanHLlYfy9c6FN9B9McQ== - -"@types/node@^16.11.27": - version "16.11.34" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.34.tgz#520224e4be4448c279ecad09639ab460cc441a50" - integrity sha512-UrWGDyLAlQ2Z8bNOGWTsqbP9ZcBeTYBVuTRNxXTztBy5KhWUFI3BaeDWoCP/CzV/EVGgO1NTYzv9ZytBI9GAEw== - -"@types/normalize-package-data@^2.4.0": - version "2.4.1" - resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" - integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== - -"@types/parse-json@^4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" - integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== - -"@types/pluralize@^0.0.29": - version "0.0.29" - resolved "https://registry.yarnpkg.com/@types/pluralize/-/pluralize-0.0.29.tgz#6ffa33ed1fc8813c469b859681d09707eb40d03c" - integrity sha512-BYOID+l2Aco2nBik+iYS4SZX0Lf20KPILP5RGmM1IgzdwNdTs0eebiFriOPcej1sX9mLnSoiNte5zcFxssgpGA== - -"@types/prettier@^2.0.0": - version "2.4.1" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.1.tgz#e1303048d5389563e130f5bdd89d37a99acb75eb" - integrity sha512-Fo79ojj3vdEZOHg3wR9ksAMRz4P3S5fDB5e/YWZiFnyFQI1WY2Vftu9XoXVVtJfxB7Bpce/QTqWSSntkz2Znrw== - -"@types/qs@*": - version "6.9.7" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" - integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== - -"@types/range-parser@*": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" - integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== - -"@types/rc@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@types/rc/-/rc-1.2.1.tgz#d2d0c2778b2a54fffe018f69b5091b4936ca7263" - integrity sha512-+TRLFmHLnpoV0uw4O/PzqMbPT6bhQM0q2KO0l+R7M3sHYRndPpNL6kv8p7Ee9ZxgQ6noYB18/t+heQi7eijOHA== - dependencies: - "@types/minimist" "*" - -"@types/registry-auth-token@^4.2.1": - version "4.2.1" - resolved "https://registry.yarnpkg.com/@types/registry-auth-token/-/registry-auth-token-4.2.1.tgz#6e83d9353bdc2c7183eb9e86fd0bac5f33d3c368" - integrity sha512-VtTUcUaJGiJtlBKYwwFIOSvrcnuKmpPGO+x56XijNZnaDpnzKh2VwoTw5hewrOMW2BgjoU+uFbVAvSCW2FpWmA== - -"@types/semver@^7.3.9": - version "7.3.9" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.9.tgz#152c6c20a7688c30b967ec1841d31ace569863fc" - integrity sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ== - -"@types/serve-static@*": - version "1.13.10" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.10.tgz#f5e0ce8797d2d7cc5ebeda48a52c96c4fa47a8d9" - integrity sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ== - dependencies: - "@types/mime" "^1" - "@types/node" "*" - -"@types/set-cookie-parser@^2.4.0": - version "2.4.1" - resolved "https://registry.yarnpkg.com/@types/set-cookie-parser/-/set-cookie-parser-2.4.1.tgz#49403d3150f6f296da8e51b3e9e7e562eaf105b4" - integrity sha512-N0IWe4vT1w5IOYdN9c9PNpQniHS+qe25W4tj4vfhJDJ9OkvA/YA55YUhaC+HNmMMeLlOSnBW9UMno0qlt5xu3Q== - dependencies: - "@types/node" "*" - -"@types/stack-utils@^2.0.0": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" - integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== - -"@types/through@*": - version "0.0.30" - resolved "https://registry.yarnpkg.com/@types/through/-/through-0.0.30.tgz#e0e42ce77e897bd6aead6f6ea62aeb135b8a3895" - integrity sha512-FvnCJljyxhPM3gkRgWmxmDZyAQSiBQQWLI0A0VFL0K7W1oRUrPJSqNO0NvTnLkBcotdlp3lKvaT0JrnyRDkzOg== - dependencies: - "@types/node" "*" - -"@types/uuid@^8.3.0": - version "8.3.1" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.1.tgz#1a32969cf8f0364b3d8c8af9cc3555b7805df14f" - integrity sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg== - -"@types/yargs-parser@*": - version "20.2.1" - resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-20.2.1.tgz#3b9ce2489919d9e4fea439b76916abc34b2df129" - integrity sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw== - -"@types/yargs@^15.0.0": - version "15.0.14" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.14.tgz#26d821ddb89e70492160b66d10a0eb6df8f6fb06" - integrity sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ== - dependencies: - "@types/yargs-parser" "*" - -"@types/yargs@^17.0.10": - version "17.0.10" - resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-17.0.10.tgz#591522fce85d8739bca7b8bb90d048e4478d186a" - integrity sha512-gmEaFwpj/7f/ROdtIlci1R1VYU1J4j95m8T+Tj3iBgiBFKg1foE/PSl93bBd5T9LDXNPo8UlNN6W0qwD8O5OaA== - dependencies: - "@types/yargs-parser" "*" - -"@types/yauzl@^2.9.1": - version "2.9.2" - resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.2.tgz#c48e5d56aff1444409e39fa164b0b4d4552a7b7a" - integrity sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA== - dependencies: - "@types/node" "*" - -"@webassemblyjs/ast@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" - integrity sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw== - dependencies: - "@webassemblyjs/helper-numbers" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - -"@webassemblyjs/floating-point-hex-parser@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz#f6c61a705f0fd7a6aecaa4e8198f23d9dc179e4f" - integrity sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ== - -"@webassemblyjs/helper-api-error@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz#1a63192d8788e5c012800ba6a7a46c705288fd16" - integrity sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg== - -"@webassemblyjs/helper-buffer@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz#832a900eb444884cde9a7cad467f81500f5e5ab5" - integrity sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA== - -"@webassemblyjs/helper-numbers@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz#64d81da219fbbba1e3bd1bfc74f6e8c4e10a62ae" - integrity sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ== - dependencies: - "@webassemblyjs/floating-point-hex-parser" "1.11.1" - "@webassemblyjs/helper-api-error" "1.11.1" - "@xtuc/long" "4.2.2" - -"@webassemblyjs/helper-wasm-bytecode@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz#f328241e41e7b199d0b20c18e88429c4433295e1" - integrity sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q== - -"@webassemblyjs/helper-wasm-section@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz#21ee065a7b635f319e738f0dd73bfbda281c097a" - integrity sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" - -"@webassemblyjs/ieee754@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz#963929e9bbd05709e7e12243a099180812992614" - integrity sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ== - dependencies: - "@xtuc/ieee754" "^1.2.0" - -"@webassemblyjs/leb128@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.1.tgz#ce814b45574e93d76bae1fb2644ab9cdd9527aa5" - integrity sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw== - dependencies: - "@xtuc/long" "4.2.2" - -"@webassemblyjs/utf8@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.1.tgz#d1f8b764369e7c6e6bae350e854dec9a59f0a3ff" - integrity sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ== - -"@webassemblyjs/wasm-edit@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz#ad206ebf4bf95a058ce9880a8c092c5dec8193d6" - integrity sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/helper-wasm-section" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" - "@webassemblyjs/wasm-opt" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - "@webassemblyjs/wast-printer" "1.11.1" - -"@webassemblyjs/wasm-gen@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz#86c5ea304849759b7d88c47a32f4f039ae3c8f76" - integrity sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/ieee754" "1.11.1" - "@webassemblyjs/leb128" "1.11.1" - "@webassemblyjs/utf8" "1.11.1" - -"@webassemblyjs/wasm-opt@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz#657b4c2202f4cf3b345f8a4c6461c8c2418985f2" - integrity sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-buffer" "1.11.1" - "@webassemblyjs/wasm-gen" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - -"@webassemblyjs/wasm-parser@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz#86ca734534f417e9bd3c67c7a1c75d8be41fb199" - integrity sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/helper-api-error" "1.11.1" - "@webassemblyjs/helper-wasm-bytecode" "1.11.1" - "@webassemblyjs/ieee754" "1.11.1" - "@webassemblyjs/leb128" "1.11.1" - "@webassemblyjs/utf8" "1.11.1" - -"@webassemblyjs/wast-printer@1.11.1": - version "1.11.1" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz#d0c73beda8eec5426f10ae8ef55cee5e7084c2f0" - integrity sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg== - dependencies: - "@webassemblyjs/ast" "1.11.1" - "@xtuc/long" "4.2.2" - -"@xmldom/xmldom@^0.7.2": - version "0.7.5" - resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.7.5.tgz#09fa51e356d07d0be200642b0e4f91d8e6dd408d" - integrity sha512-V3BIhmY36fXZ1OtVcI9W+FxQqxVLsPKcNjWigIaa81dLC9IolJl5Mt4Cvhmr0flUnjSpTdrbMTSbXqYqV5dT6A== - -"@xtuc/ieee754@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" - integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== - -"@xtuc/long@4.2.2": - version "4.2.2" - resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" - integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== - -JSONStream@^1.0.4: - version "1.3.5" - resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" - integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ== - dependencies: - jsonparse "^1.2.0" - through ">=2.2.7 <3" - -abab@^2.0.3, abab@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a" - integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== - -accepts@~1.3.7: - version "1.3.7" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" - integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== - dependencies: - mime-types "~2.1.24" - negotiator "0.6.2" - -acorn-globals@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45" - integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg== - dependencies: - acorn "^7.1.1" - acorn-walk "^7.1.1" - -acorn-import-assertions@^1.7.6: - version "1.8.0" - resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9" - integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== - -acorn-walk@^7.1.1: - version "7.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" - integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== - -acorn-walk@^8.1.1: - version "8.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" - integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== - -acorn@^7.1.1: - version "7.4.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" - integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== - -acorn@^8.2.4, acorn@^8.4.1: - version "8.5.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.5.0.tgz#4512ccb99b3698c752591e9bb4472e38ad43cee2" - integrity sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q== - -agent-base@6: - version "6.0.2" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" - integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== - dependencies: - debug "4" - -ajv-keywords@^3.5.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" - integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== - -ajv@^6.12.5, ajv@^6.12.6: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - -ansi-escapes@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" - integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== - -ansi-escapes@^4.2.1: - version "4.3.2" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" - integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== - dependencies: - type-fest "^0.21.3" - -ansi-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" - integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= - -ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== - -ansi-regex@^5.0.0, ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== - dependencies: - color-convert "^1.9.0" - -ansi-styles@^4.0.0, ansi-styles@^4.1.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -anymatch@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" - integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== - dependencies: - micromatch "^3.1.4" - normalize-path "^2.1.1" - -anymatch@^3.0.3, anymatch@~3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" - integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -arg@^4.1.0: - version "4.1.3" - resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" - integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== - -argparse@^1.0.7: - version "1.0.10" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" - integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== - dependencies: - sprintf-js "~1.0.2" - -args@^5.0.1: - version "5.0.3" - resolved "https://registry.yarnpkg.com/args/-/args-5.0.3.tgz#943256db85021a85684be2f0882f25d796278702" - integrity sha512-h6k/zfFgusnv3i5TU08KQkVKuCPBtL/PWQbWkHUxvJrZ2nAyeaUupneemcrgn1xmqxPQsPIzwkUhOpoqPDRZuA== - dependencies: - camelcase "5.0.0" - chalk "2.4.2" - leven "2.1.0" - mri "1.1.4" - -argv-formatter@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/argv-formatter/-/argv-formatter-1.0.0.tgz#a0ca0cbc29a5b73e836eebe1cbf6c5e0e4eb82f9" - integrity sha1-oMoMvCmltz6Dbuvhy/bF4OTrgvk= - -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= - -arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= - -array-ify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" - integrity sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4= - -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= - -arrify@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" - integrity sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0= - -assign-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= - -atob@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" - integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== - -atomic-sleep@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" - integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== - -babel-jest@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-26.6.3.tgz#d87d25cb0037577a0c89f82e5755c5d293c01056" - integrity sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA== - dependencies: - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/babel__core" "^7.1.7" - babel-plugin-istanbul "^6.0.0" - babel-preset-jest "^26.6.2" - chalk "^4.0.0" - graceful-fs "^4.2.4" - slash "^3.0.0" - -babel-plugin-istanbul@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz#e159ccdc9af95e0b570c75b4573b7c34d671d765" - integrity sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ== - dependencies: - "@babel/helper-plugin-utils" "^7.0.0" - "@istanbuljs/load-nyc-config" "^1.0.0" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-instrument "^4.0.0" - test-exclude "^6.0.0" - -babel-plugin-jest-hoist@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz#8185bd030348d254c6d7dd974355e6a28b21e62d" - integrity sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw== - dependencies: - "@babel/template" "^7.3.3" - "@babel/types" "^7.3.3" - "@types/babel__core" "^7.0.0" - "@types/babel__traverse" "^7.0.6" - -babel-preset-current-node-syntax@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b" - integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ== - dependencies: - "@babel/plugin-syntax-async-generators" "^7.8.4" - "@babel/plugin-syntax-bigint" "^7.8.3" - "@babel/plugin-syntax-class-properties" "^7.8.3" - "@babel/plugin-syntax-import-meta" "^7.8.3" - "@babel/plugin-syntax-json-strings" "^7.8.3" - "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" - "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" - "@babel/plugin-syntax-numeric-separator" "^7.8.3" - "@babel/plugin-syntax-object-rest-spread" "^7.8.3" - "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" - "@babel/plugin-syntax-optional-chaining" "^7.8.3" - "@babel/plugin-syntax-top-level-await" "^7.8.3" - -babel-preset-jest@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz#747872b1171df032252426586881d62d31798fee" - integrity sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ== - dependencies: - babel-plugin-jest-hoist "^26.6.2" - babel-preset-current-node-syntax "^1.0.0" - -balanced-match@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" - integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== - -base64-js@^1.3.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - -binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== - -bl@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" - integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== - dependencies: - buffer "^5.5.0" - inherits "^2.0.4" - readable-stream "^3.4.0" - -body-parser@1.19.0: - version "1.19.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" - integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== - dependencies: - bytes "3.1.0" - content-type "~1.0.4" - debug "2.6.9" - depd "~1.1.2" - http-errors "1.7.2" - iconv-lite "0.4.24" - on-finished "~2.3.0" - qs "6.7.0" - raw-body "2.4.0" - type-is "~1.6.17" - -brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - -braces@^2.3.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" - -braces@^3.0.1, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -browser-process-hrtime@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626" - integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow== - -browserslist@^4.14.5, browserslist@^4.16.6: - version "4.17.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.3.tgz#2844cd6eebe14d12384b0122d217550160d2d624" - integrity sha512-59IqHJV5VGdcJZ+GZ2hU5n4Kv3YiASzW6Xk5g9tf5a/MAzGeFwgGWU39fVzNIOVcgB3+Gp+kiQu0HEfTVU/3VQ== - dependencies: - caniuse-lite "^1.0.30001264" - electron-to-chromium "^1.3.857" - escalade "^3.1.1" - node-releases "^1.1.77" - picocolors "^0.2.1" - -bs-logger@0.x: - version "0.2.6" - resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" - integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== - dependencies: - fast-json-stable-stringify "2.x" - -bser@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" - integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ== - dependencies: - node-int64 "^0.4.0" - -buffer-crc32@~0.2.3: - version "0.2.13" - resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= - -buffer-from@1.x, buffer-from@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" - integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== - -buffer@^5.5.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" - integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== - dependencies: - base64-js "^1.3.1" - ieee754 "^1.1.13" - -bytes@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" - integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== - -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== - dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - -cachedir@2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.2.0.tgz#19afa4305e05d79e417566882e0c8f960f62ff0e" - integrity sha512-VvxA0xhNqIIfg0V9AmJkDg91DaJwryutH5rVEZAhcNi4iJFj9f+QxmAjgK1LT9I8OgToX27fypX6/MeCXVbBjQ== - -callsites@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" - integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== - -camelcase-keys@^6.2.2: - version "6.2.2" - resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-6.2.2.tgz#5e755d6ba51aa223ec7d3d52f25778210f9dc3c0" - integrity sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg== - dependencies: - camelcase "^5.3.1" - map-obj "^4.0.0" - quick-lru "^4.0.1" - -camelcase@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.0.0.tgz#03295527d58bd3cd4aa75363f35b2e8d97be2f42" - integrity sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA== - -camelcase@^5.0.0, camelcase@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -camelcase@^6.0.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809" - integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg== - -caniuse-lite@^1.0.30001264: - version "1.0.30001265" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001265.tgz#0613c9e6c922e422792e6fcefdf9a3afeee4f8c3" - integrity sha512-YzBnspggWV5hep1m9Z6sZVLOt7vrju8xWooFAgN6BA5qvy98qPAPb7vNUzypFaoh2pb3vlfzbDO8tB57UPGbtw== - -capture-exit@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" - integrity sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g== - dependencies: - rsvp "^4.8.4" - -chalk@2.4.2, chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - -chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - -char-regex@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf" - integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw== - -chardet@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" - integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== - -charenc@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" - integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc= - -chokidar@^3.4.2: - version "3.5.2" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" - integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -chrome-trace-event@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" - integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== - -ci-info@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" - integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== - -cjs-module-lexer@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz#4186fcca0eae175970aee870b9fe2d6cf8d5655f" - integrity sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw== - -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" - -cli-cursor@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" - integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= - dependencies: - restore-cursor "^2.0.0" - -cli-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" - integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw== - dependencies: - restore-cursor "^3.1.0" - -cli-spinners@^2.5.0: - version "2.6.1" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.6.1.tgz#adc954ebe281c37a6319bfa401e6dd2488ffb70d" - integrity sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g== - -cli-width@^2.0.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.1.tgz#b0433d0b4e9c847ef18868a4ef16fd5fc8271c48" - integrity sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw== - -cli-width@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" - integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== - -cliui@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" - integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^6.2.0" - -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" - -clone-deep@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" - integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== - dependencies: - is-plain-object "^2.0.4" - kind-of "^6.0.2" - shallow-clone "^3.0.0" - -clone@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" - integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= - -co@^4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" - integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= - -collect-v8-coverage@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" - integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== - -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" - -color-convert@^1.9.0: - version "1.9.3" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" - integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== - dependencies: - color-name "1.1.3" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= - -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -colorette@^2.0.7: - version "2.0.16" - resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.16.tgz#713b9af84fdb000139f04546bd4a93f62a5085da" - integrity sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g== - -combined-stream@^1.0.8: - version "1.0.8" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" - integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== - dependencies: - delayed-stream "~1.0.0" - -commander@^2.20.0: - version "2.20.3" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" - integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== - -commander@^6.1.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" - integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== - -commitizen@^4.0.3, commitizen@^4.2.4: - version "4.2.4" - resolved "https://registry.yarnpkg.com/commitizen/-/commitizen-4.2.4.tgz#a3e5b36bd7575f6bf6e7aa19dbbf06b0d8f37165" - integrity sha512-LlZChbDzg3Ir3O2S7jSo/cgWp5/QwylQVr59K4xayVq8S4/RdKzSyJkghAiZZHfhh5t4pxunUoyeg0ml1q/7aw== - dependencies: - cachedir "2.2.0" - cz-conventional-changelog "3.2.0" - dedent "0.7.0" - detect-indent "6.0.0" - find-node-modules "^2.1.2" - find-root "1.1.0" - fs-extra "8.1.0" - glob "7.1.4" - inquirer "6.5.2" - is-utf8 "^0.2.1" - lodash "^4.17.20" - minimist "1.2.5" - strip-bom "4.0.0" - strip-json-comments "3.0.1" - -compare-func@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/compare-func/-/compare-func-2.0.0.tgz#fb65e75edbddfd2e568554e8b5b05fff7a51fcb3" - integrity sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA== - dependencies: - array-ify "^1.0.0" - dot-prop "^5.1.0" - -component-emitter@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== - -concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= - -content-disposition@0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" - integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== - dependencies: - safe-buffer "5.1.2" - -content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" - integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== - -conventional-changelog-angular@^5.0.11: - version "5.0.13" - resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz#896885d63b914a70d4934b59d2fe7bde1832b28c" - integrity sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA== - dependencies: - compare-func "^2.0.0" - q "^1.5.1" - -conventional-changelog-conventionalcommits@^4.3.1: - version "4.6.3" - resolved "https://registry.yarnpkg.com/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-4.6.3.tgz#0765490f56424b46f6cb4db9135902d6e5a36dc2" - integrity sha512-LTTQV4fwOM4oLPad317V/QNQ1FY4Hju5qeBIM1uTHbrnCE+Eg4CdRZ3gO2pUeR+tzWdp80M2j3qFFEDWVqOV4g== - dependencies: - compare-func "^2.0.0" - lodash "^4.17.15" - q "^1.5.1" - -conventional-commit-types@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/conventional-commit-types/-/conventional-commit-types-3.0.0.tgz#7c9214e58eae93e85dd66dbfbafe7e4fffa2365b" - integrity sha512-SmmCYnOniSsAa9GqWOeLqc179lfr5TRu5b4QFDkbsrJ5TZjPJx85wtOr3zn+1dbeNiXDKGPbZ72IKbPhLXh/Lg== - -conventional-commits-parser@^3.2.2, conventional-commits-parser@^3.2.4: - version "3.2.4" - resolved "https://registry.yarnpkg.com/conventional-commits-parser/-/conventional-commits-parser-3.2.4.tgz#a7d3b77758a202a9b2293d2112a8d8052c740972" - integrity sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q== - dependencies: - JSONStream "^1.0.4" - is-text-path "^1.0.1" - lodash "^4.17.15" - meow "^8.0.0" - split2 "^3.0.0" - through2 "^4.0.0" - -convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369" - integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA== - dependencies: - safe-buffer "~5.1.1" - -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= - -cookie@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" - integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== - -cookie@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" - integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== - -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= - -core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - -cosmiconfig-typescript-loader@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-1.0.2.tgz#257b5bc3e2c18c280973aa3ff4ae423ac29ce9b4" - integrity sha512-27ZehvijYqAKVzta5xtZBS3PAliC8CmnWkGXN0vgxAZz7yqxpMjf3aG7flxF5rEiu8FAD7nZZXtOI+xUGn+bVg== - dependencies: - cosmiconfig "^7" - ts-node "^10.4.0" - -cosmiconfig@^7, cosmiconfig@^7.0.0: - version "7.0.1" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d" - integrity sha512-a1YWNUV2HwGimB7dU2s1wUMurNKjpx60HxBB6xUM8Re+2s1g1IIfJvFR0/iCF+XHdE0GMTKTuLR32UQff4TEyQ== - dependencies: - "@types/parse-json" "^4.0.0" - import-fresh "^3.2.1" - parse-json "^5.0.0" - path-type "^4.0.0" - yaml "^1.10.0" - -create-require@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" - integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== - -cross-spawn@^6.0.0: - version "6.0.5" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" - integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== - dependencies: - nice-try "^1.0.4" - path-key "^2.0.1" - semver "^5.5.0" - shebang-command "^1.2.0" - which "^1.2.9" - -cross-spawn@^7.0.0, cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== - dependencies: - path-key "^3.1.0" - shebang-command "^2.0.0" - which "^2.0.1" - -crypt@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" - integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= - -cssom@^0.4.4: - version "0.4.4" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" - integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw== - -cssom@~0.3.6: - version "0.3.8" - resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a" - integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg== - -cssstyle@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852" - integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A== - dependencies: - cssom "~0.3.6" - -cz-conventional-changelog@3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/cz-conventional-changelog/-/cz-conventional-changelog-3.2.0.tgz#6aef1f892d64113343d7e455529089ac9f20e477" - integrity sha512-yAYxeGpVi27hqIilG1nh4A9Bnx4J3Ov+eXy4koL3drrR+IO9GaWPsKjik20ht608Asqi8TQPf0mczhEeyAtMzg== - dependencies: - chalk "^2.4.1" - commitizen "^4.0.3" - conventional-commit-types "^3.0.0" - lodash.map "^4.5.1" - longest "^2.0.1" - word-wrap "^1.0.3" - optionalDependencies: - "@commitlint/load" ">6.1.1" - -cz-conventional-changelog@3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/cz-conventional-changelog/-/cz-conventional-changelog-3.3.0.tgz#9246947c90404149b3fe2cf7ee91acad3b7d22d2" - integrity sha512-U466fIzU5U22eES5lTNiNbZ+d8dfcHcssH4o7QsdWaCcRs/feIPCxKYSWkYBNs5mny7MvEfwpTLWjvbm94hecw== - dependencies: - chalk "^2.4.1" - commitizen "^4.0.3" - conventional-commit-types "^3.0.0" - lodash.map "^4.5.1" - longest "^2.0.1" - word-wrap "^1.0.3" - optionalDependencies: - "@commitlint/load" ">6.1.1" - -dargs@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/dargs/-/dargs-7.0.0.tgz#04015c41de0bcb69ec84050f3d9be0caf8d6d5cc" - integrity sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg== - -data-urls@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b" - integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ== - dependencies: - abab "^2.0.3" - whatwg-mimetype "^2.3.0" - whatwg-url "^8.0.0" - -date-fns@^2.21.1: - version "2.25.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.25.0.tgz#8c5c8f1d958be3809a9a03f4b742eba894fc5680" - integrity sha512-ovYRFnTrbGPD4nqaEqescPEv1mNwvt+UTqI3Ay9SzNtey9NZnYu6E2qCcBBgJ6/2VF1zGGygpyTDITqpQQ5e+w== - -dateformat@^4.6.3: - version "4.6.3" - resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.6.3.tgz#556fa6497e5217fedb78821424f8a1c22fa3f4b5" - integrity sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA== - -debug@2.6.9, debug@^2.2.0, debug@^2.3.3: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== - dependencies: - ms "2.0.0" - -debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" - integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== - dependencies: - ms "2.1.2" - -decamelize-keys@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" - integrity sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk= - dependencies: - decamelize "^1.1.0" - map-obj "^1.0.0" - -decamelize@^1.1.0, decamelize@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= - -decimal.js@^10.2.1: - version "10.3.1" - resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783" - integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== - -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= - -dedent@0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" - integrity sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw= - -deep-extend@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" - integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== - -deep-is@~0.1.3: - version "0.1.4" - resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" - integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== - -deepmerge@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" - integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== - -defaults@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" - integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= - dependencies: - clone "^1.0.2" - -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= - -depd@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= - -destroy@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" - integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= - -detect-file@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" - integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc= - -detect-indent@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.0.0.tgz#0abd0f549f69fc6659a254fe96786186b6f528fd" - integrity sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA== - -detect-newline@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651" - integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA== - -diff-sequences@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" - integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q== - -diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== - -domexception@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304" - integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg== - dependencies: - webidl-conversions "^5.0.0" - -dot-prop@^5.1.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/dot-prop/-/dot-prop-5.3.0.tgz#90ccce708cd9cd82cc4dc8c3ddd9abdd55b20e88" - integrity sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q== - dependencies: - is-obj "^2.0.0" - -duplexer2@~0.1.0: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" - integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME= - dependencies: - readable-stream "^2.0.2" - -duplexify@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.2.tgz#18b4f8d28289132fa0b9573c898d9f903f81c7b0" - integrity sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw== - dependencies: - end-of-stream "^1.4.1" - inherits "^2.0.3" - readable-stream "^3.1.1" - stream-shift "^1.0.0" - -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= - -electron-to-chromium@^1.3.857: - version "1.3.860" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.860.tgz#d612e54ed75fa524c12af8da3ad8121ebfe2802b" - integrity sha512-gWwGZ+Wv4Mou2SJRH6JQzhTPjL5f95SX7n6VkLTQ/Q/INsZLZNQ1vH2GlZjozKyvT0kkFuCmWTwIoCj+/hUDPw== - -emittery@^0.7.1: - version "0.7.2" - resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.7.2.tgz#25595908e13af0f5674ab419396e2fb394cdfa82" - integrity sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= - -end-of-stream@^1.1.0, end-of-stream@^1.4.1: - version "1.4.4" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" - integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== - dependencies: - once "^1.4.0" - -enhanced-resolve@^5.8.3: - version "5.8.3" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.8.3.tgz#6d552d465cce0423f5b3d718511ea53826a7b2f0" - integrity sha512-EGAbGvH7j7Xt2nc0E7D99La1OiEs8LnyimkRgwExpUMScN6O+3x9tIWs7PLQZVNx4YD+00skHXPXi1yQHpAmZA== - dependencies: - graceful-fs "^4.2.4" - tapable "^2.2.0" - -error-ex@^1.3.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf" - integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g== - dependencies: - is-arrayish "^0.2.1" - -es-module-lexer@^0.9.0: - version "0.9.2" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.2.tgz#d0a8c72c5d904014111fac7fab4c92b9ac545564" - integrity sha512-YkAGWqxZq2B4FxQ5y687UwywDwvLQhIMCZ+SDU7ZW729SDHOEI6wVFXwTRecz+yiwJzCsVwC6V7bxyNbZSB1rg== - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== - -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= - -escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= - -escape-string-regexp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" - integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== - -escodegen@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd" - integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw== - dependencies: - esprima "^4.0.1" - estraverse "^5.2.0" - esutils "^2.0.2" - optionator "^0.8.1" - optionalDependencies: - source-map "~0.6.1" - -eslint-scope@5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - -esprima@^4.0.0, esprima@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - -esrecurse@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" - integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== - dependencies: - estraverse "^5.2.0" - -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - -estraverse@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880" - integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ== - -esutils@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" - integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== - -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= - -events@^3.2.0, events@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" - integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== - -exec-sh@^0.3.2: - version "0.3.6" - resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.6.tgz#ff264f9e325519a60cb5e273692943483cca63bc" - integrity sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w== - -execa@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" - integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== - dependencies: - cross-spawn "^6.0.0" - get-stream "^4.0.0" - is-stream "^1.1.0" - npm-run-path "^2.0.0" - p-finally "^1.0.0" - signal-exit "^3.0.0" - strip-eof "^1.0.0" - -execa@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" - integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== - dependencies: - cross-spawn "^7.0.0" - get-stream "^5.0.0" - human-signals "^1.1.1" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.0" - onetime "^5.1.0" - signal-exit "^3.0.2" - strip-final-newline "^2.0.0" - -execa@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" - integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== - dependencies: - cross-spawn "^7.0.3" - get-stream "^6.0.0" - human-signals "^2.1.0" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.1" - onetime "^5.1.2" - signal-exit "^3.0.3" - strip-final-newline "^2.0.0" - -exit@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" - integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= - -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -expand-tilde@^2.0.0, expand-tilde@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" - integrity sha1-l+gBqgUt8CRU3kawK/YhZCzchQI= - dependencies: - homedir-polyfill "^1.0.1" - -expect@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/expect/-/expect-26.6.2.tgz#c6b996bf26bf3fe18b67b2d0f51fc981ba934417" - integrity sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA== - dependencies: - "@jest/types" "^26.6.2" - ansi-styles "^4.0.0" - jest-get-type "^26.3.0" - jest-matcher-utils "^26.6.2" - jest-message-util "^26.6.2" - jest-regex-util "^26.0.0" - -express@^4.17.1: - version "4.17.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" - integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== - dependencies: - accepts "~1.3.7" - array-flatten "1.1.1" - body-parser "1.19.0" - content-disposition "0.5.3" - content-type "~1.0.4" - cookie "0.4.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "~1.1.2" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "~1.1.2" - fresh "0.5.2" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "~2.3.0" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.5" - qs "6.7.0" - range-parser "~1.2.1" - safe-buffer "5.1.2" - send "0.17.1" - serve-static "1.14.1" - setprototypeof "1.1.1" - statuses "~1.5.0" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" - -external-editor@^3.0.3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" - integrity sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew== - dependencies: - chardet "^0.7.0" - iconv-lite "^0.4.24" - tmp "^0.0.33" - -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -extract-zip@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" - integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== - dependencies: - debug "^4.1.1" - get-stream "^5.1.0" - yauzl "^2.10.0" - optionalDependencies: - "@types/yauzl" "^2.9.1" - -faker@^5.5.3: - version "5.5.3" - resolved "https://registry.yarnpkg.com/faker/-/faker-5.5.3.tgz#c57974ee484431b25205c2c8dc09fda861e51e0e" - integrity sha512-wLTv2a28wjUyWkbnX7u/ABZBkUkIF2fCd73V6P2oFqEGEktDfzWx4UxrSqtPRw0xPRAcjeAOIiJWqZm3pP4u3g== - -fast-deep-equal@^3.1.1: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - -fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" - integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== - -fast-levenshtein@~2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" - integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= - -fast-redact@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.1.1.tgz#790fcff8f808c2e12fabbfb2be5cb2deda448fa0" - integrity sha512-odVmjC8x8jNeMZ3C+rPMESzXVSEU8tSWSHv9HFxP2mm89G/1WwqhrerJDQm9Zus8X6aoRgQDThKqptdNA6bt+A== - -fast-safe-stringify@^2.0.7: - version "2.1.1" - resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" - integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== - -fb-watchman@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85" - integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg== - dependencies: - bser "2.1.1" - -fd-slicer@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" - integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4= - dependencies: - pend "~1.2.0" - -figures@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" - integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= - dependencies: - escape-string-regexp "^1.0.5" - -figures@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" - integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== - dependencies: - escape-string-regexp "^1.0.5" - -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= - dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -finalhandler@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" - integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "~2.3.0" - parseurl "~1.3.3" - statuses "~1.5.0" - unpipe "~1.0.0" - -find-node-modules@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/find-node-modules/-/find-node-modules-2.1.2.tgz#57565a3455baf671b835bc6b2134a9b938b9c53c" - integrity sha512-x+3P4mbtRPlSiVE1Qco0Z4YLU8WFiFcuWTf3m75OV9Uzcfs2Bg+O9N+r/K0AnmINBW06KpfqKwYJbFlFq4qNug== - dependencies: - findup-sync "^4.0.0" - merge "^2.1.0" - -find-root@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" - integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== - -find-up@^4.0.0, find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - -findup-sync@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-4.0.0.tgz#956c9cdde804052b881b428512905c4a5f2cdef0" - integrity sha512-6jvvn/12IC4quLBL1KNokxC7wWTvYncaVUYSoxWw7YykPLuRrnv4qdHcSOywOI5RpkOVGeQRtWM8/q+G6W6qfQ== - dependencies: - detect-file "^1.0.0" - is-glob "^4.0.0" - micromatch "^4.0.2" - resolve-dir "^1.0.1" - -for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= - -form-data@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" - integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.8" - mime-types "^2.1.12" - -forwarded@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" - integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== - -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= - dependencies: - map-cache "^0.2.2" - -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= - -fs-extra@8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" - integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^4.0.0" - universalify "^0.1.0" - -fs-extra@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.0.0.tgz#9ff61b655dde53fb34a82df84bb214ce802e17c1" - integrity sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ== - dependencies: - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - -fs-monkey@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.3.tgz#ae3ac92d53bb328efe0e9a1d9541f6ad8d48e2d3" - integrity sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q== - -fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= - -fsevents@^2.1.2, fsevents@~2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -gensync@^1.0.0-beta.2: - version "1.0.0-beta.2" - resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" - integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== - -get-caller-file@^2.0.1, get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-package-type@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a" - integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q== - -get-stream@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" - integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== - dependencies: - pump "^3.0.0" - -get-stream@^5.0.0, get-stream@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" - integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== - dependencies: - pump "^3.0.0" - -get-stream@^6.0.0, get-stream@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" - integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg== - -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= - -git-log-parser@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/git-log-parser/-/git-log-parser-1.2.0.tgz#2e6a4c1b13fc00028207ba795a7ac31667b9fd4a" - integrity sha1-LmpMGxP8AAKCB7p5WnrDFme5/Uo= - dependencies: - argv-formatter "~1.0.0" - spawn-error-forwarder "~1.0.0" - split2 "~1.0.0" - stream-combiner2 "~1.1.1" - through2 "~2.0.0" - traverse "~0.6.6" - -git-raw-commits@^2.0.0: - version "2.0.11" - resolved "https://registry.yarnpkg.com/git-raw-commits/-/git-raw-commits-2.0.11.tgz#bc3576638071d18655e1cc60d7f524920008d723" - integrity sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A== - dependencies: - dargs "^7.0.0" - lodash "^4.17.15" - meow "^8.0.0" - split2 "^3.0.0" - through2 "^4.0.0" - -glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob-to-regexp@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" - integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== - -glob@7.1.4: - version "7.1.4" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" - integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: - version "7.2.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" - integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - -global-dirs@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" - integrity sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU= - dependencies: - ini "^1.3.4" - -global-modules@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" - integrity sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg== - dependencies: - global-prefix "^1.0.1" - is-windows "^1.0.1" - resolve-dir "^1.0.0" - -global-prefix@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" - integrity sha1-2/dDxsFJklk8ZVVoy2btMsASLr4= - dependencies: - expand-tilde "^2.0.2" - homedir-polyfill "^1.0.1" - ini "^1.3.4" - is-windows "^1.0.1" - which "^1.2.14" - -globals@^11.1.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== - -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4: - version "4.2.8" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" - integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== - -graphql@^15.5.0, graphql@^15.5.1: - version "15.6.1" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.6.1.tgz#9125bdf057553525da251e19e96dab3d3855ddfc" - integrity sha512-3i5lu0z6dRvJ48QP9kFxBkJ7h4Kso7PS8eahyTFz5Jm6CvQfLtNIE8LX9N6JLnXTuwR+sIYnXzaWp6anOg0QQw== - -growly@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" - integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= - -hard-rejection@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" - integrity sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA== - -has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= - -has-flag@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" - integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== - -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= - -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -headers-utils@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/headers-utils/-/headers-utils-3.0.2.tgz#dfc65feae4b0e34357308aefbcafa99c895e59ef" - integrity sha512-xAxZkM1dRyGV2Ou5bzMxBPNLoRCjcX+ya7KSWybQD2KwLphxsapUVK6x/02o7f4VU6GPSXch9vNY2+gkU8tYWQ== - -homedir-polyfill@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" - integrity sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA== - dependencies: - parse-passwd "^1.0.0" - -hosted-git-info@^2.1.4: - version "2.8.9" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" - integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== - -hosted-git-info@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-4.0.2.tgz#5e425507eede4fea846b7262f0838456c4209961" - integrity sha512-c9OGXbZ3guC/xOlCg1Ci/VgWlwsqDv1yMQL1CWqXDL0hDjXuNcq0zuR4xqPSuasI3kqFDhqSyTjREz5gzq0fXg== - dependencies: - lru-cache "^6.0.0" - -html-encoding-sniffer@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3" - integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ== - dependencies: - whatwg-encoding "^1.0.5" - -html-escaper@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" - integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== - -http-errors@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" - integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - -http-errors@~1.7.2: - version "1.7.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" - integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== - dependencies: - depd "~1.1.2" - inherits "2.0.4" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - -http-proxy-agent@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" - integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== - dependencies: - "@tootallnate/once" "1" - agent-base "6" - debug "4" - -https-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" - integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== - dependencies: - agent-base "6" - debug "4" - -human-signals@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3" - integrity sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw== - -human-signals@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" - integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== - -iconv-lite@0.4.24, iconv-lite@^0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - -ieee754@^1.1.13: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - -import-fresh@^3.0.0, import-fresh@^3.2.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== - dependencies: - parent-module "^1.0.0" - resolve-from "^4.0.0" - -import-local@^3.0.2: - version "3.0.3" - resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.3.tgz#4d51c2c495ca9393da259ec66b62e022920211e0" - integrity sha512-bE9iaUY3CXH8Cwfan/abDKAxe1KGT9kyGsBPqf6DMK/z0a2OzAsrukeYNgIH6cH5Xr452jb1TUL8rSfCLjZ9uA== - dependencies: - pkg-dir "^4.2.0" - resolve-cwd "^3.0.0" - -imurmurhash@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" - integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= - -indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" - integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== - -inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= - dependencies: - once "^1.3.0" - wrappy "1" - -inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" - integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== - -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - -ini@^1.3.4, ini@~1.3.0: - version "1.3.8" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" - integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew== - -inquirer@6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca" - integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ== - dependencies: - ansi-escapes "^3.2.0" - chalk "^2.4.2" - cli-cursor "^2.1.0" - cli-width "^2.0.0" - external-editor "^3.0.3" - figures "^2.0.0" - lodash "^4.17.12" - mute-stream "0.0.7" - run-async "^2.2.0" - rxjs "^6.4.0" - string-width "^2.1.0" - strip-ansi "^5.1.0" - through "^2.3.6" - -inquirer@^8.1.1: - version "8.2.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-8.2.0.tgz#f44f008dd344bbfc4b30031f45d984e034a3ac3a" - integrity sha512-0crLweprevJ02tTuA6ThpoAERAGyVILC4sS74uib58Xf/zSr1/ZWtmm7D5CI+bSQEaA04f0K7idaHpQbSWgiVQ== - dependencies: - ansi-escapes "^4.2.1" - chalk "^4.1.1" - cli-cursor "^3.1.0" - cli-width "^3.0.0" - external-editor "^3.0.3" - figures "^3.0.0" - lodash "^4.17.21" - mute-stream "0.0.8" - ora "^5.4.1" - run-async "^2.4.0" - rxjs "^7.2.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - through "^2.3.6" - -ipaddr.js@1.9.1: - version "1.9.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" - integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== - -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= - dependencies: - kind-of "^3.0.2" - -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" - integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== - dependencies: - kind-of "^6.0.0" - -is-arrayish@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" - integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-buffer@^1.1.5, is-buffer@~1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - -is-ci@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" - integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== - dependencies: - ci-info "^2.0.0" - -is-core-module@^2.2.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.7.0.tgz#3c0ef7d31b4acfc574f80c58409d568a836848e3" - integrity sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ== - dependencies: - has "^1.0.3" - -is-core-module@^2.5.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.8.0.tgz#0321336c3d0925e497fd97f5d95cb114a5ccd548" - integrity sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw== - dependencies: - has "^1.0.3" - -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= - dependencies: - kind-of "^3.0.2" - -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" - integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== - dependencies: - kind-of "^6.0.0" - -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" - integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== - dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - -is-docker@^2.0.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" - integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== - dependencies: - is-plain-object "^2.0.4" - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= - -is-fullwidth-code-point@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-generator-fn@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118" - integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ== - -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-interactive@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-interactive/-/is-interactive-1.0.0.tgz#cea6e6ae5c870a7b0a0004070b7b587e0252912e" - integrity sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w== - -is-node-process@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-node-process/-/is-node-process-1.0.1.tgz#4fc7ac3a91e8aac58175fe0578abbc56f2831b23" - integrity sha512-5IcdXuf++TTNt3oGl9EBdkvndXA8gmc4bz/Y+mdEpWh3Mcn/+kOw6hI7LD5CocqJWMzeb0I0ClndRVNdEPuJXQ== - -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= - dependencies: - kind-of "^3.0.2" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -is-obj@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" - integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== - -is-plain-obj@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" - integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= - -is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - -is-potential-custom-element-name@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" - integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== - -is-stream@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= - -is-stream@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077" - integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg== - -is-text-path@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-text-path/-/is-text-path-1.0.1.tgz#4e1aa0fb51bfbcb3e92688001397202c1775b66e" - integrity sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4= - dependencies: - text-extensions "^1.0.0" - -is-typedarray@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" - integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= - -is-unicode-supported@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" - integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== - -is-utf8@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" - integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI= - -is-windows@^1.0.1, is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - -is-wsl@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" - integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== - dependencies: - is-docker "^2.0.0" - -isarray@1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - -isexe@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= - -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= - -issue-parser@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/issue-parser/-/issue-parser-6.0.0.tgz#b1edd06315d4f2044a9755daf85fdafde9b4014a" - integrity sha512-zKa/Dxq2lGsBIXQ7CUZWTHfvxPC2ej0KfO7fIPqLlHB9J2hJ7rGhZ5rilhuufylr4RXYPzJUeFjKxz305OsNlA== - dependencies: - lodash.capitalize "^4.2.1" - lodash.escaperegexp "^4.1.2" - lodash.isplainobject "^4.0.6" - lodash.isstring "^4.0.1" - lodash.uniqby "^4.7.0" - -istanbul-lib-coverage@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.1.tgz#e8900b3ed6069759229cf30f7067388d148aeb5e" - integrity sha512-GvCYYTxaCPqwMjobtVcVKvSHtAGe48MNhGjpK8LtVF8K0ISX7hCKl85LgtuaSneWVyQmaGcW3iXVV3GaZSLpmQ== - -istanbul-lib-instrument@^4.0.0, istanbul-lib-instrument@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" - integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== - dependencies: - "@babel/core" "^7.7.5" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.0.0" - semver "^6.3.0" - -istanbul-lib-report@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" - integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw== - dependencies: - istanbul-lib-coverage "^3.0.0" - make-dir "^3.0.0" - supports-color "^7.1.0" - -istanbul-lib-source-maps@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz#75743ce6d96bb86dc7ee4352cf6366a23f0b1ad9" - integrity sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg== - dependencies: - debug "^4.1.1" - istanbul-lib-coverage "^3.0.0" - source-map "^0.6.1" - -istanbul-reports@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.2.tgz#d593210e5000683750cb09fc0644e4b6e27fd53b" - integrity sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw== - dependencies: - html-escaper "^2.0.0" - istanbul-lib-report "^3.0.0" - -jest-changed-files@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-26.6.2.tgz#f6198479e1cc66f22f9ae1e22acaa0b429c042d0" - integrity sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ== - dependencies: - "@jest/types" "^26.6.2" - execa "^4.0.0" - throat "^5.0.0" - -jest-cli@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-26.6.3.tgz#43117cfef24bc4cd691a174a8796a532e135e92a" - integrity sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg== - dependencies: - "@jest/core" "^26.6.3" - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" - chalk "^4.0.0" - exit "^0.1.2" - graceful-fs "^4.2.4" - import-local "^3.0.2" - is-ci "^2.0.0" - jest-config "^26.6.3" - jest-util "^26.6.2" - jest-validate "^26.6.2" - prompts "^2.0.1" - yargs "^15.4.1" - -jest-config@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-26.6.3.tgz#64f41444eef9eb03dc51d5c53b75c8c71f645349" - integrity sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg== - dependencies: - "@babel/core" "^7.1.0" - "@jest/test-sequencer" "^26.6.3" - "@jest/types" "^26.6.2" - babel-jest "^26.6.3" - chalk "^4.0.0" - deepmerge "^4.2.2" - glob "^7.1.1" - graceful-fs "^4.2.4" - jest-environment-jsdom "^26.6.2" - jest-environment-node "^26.6.2" - jest-get-type "^26.3.0" - jest-jasmine2 "^26.6.3" - jest-regex-util "^26.0.0" - jest-resolve "^26.6.2" - jest-util "^26.6.2" - jest-validate "^26.6.2" - micromatch "^4.0.2" - pretty-format "^26.6.2" - -jest-diff@^26.0.0, jest-diff@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394" - integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA== - dependencies: - chalk "^4.0.0" - diff-sequences "^26.6.2" - jest-get-type "^26.3.0" - pretty-format "^26.6.2" - -jest-docblock@^26.0.0: - version "26.0.0" - resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-26.0.0.tgz#3e2fa20899fc928cb13bd0ff68bd3711a36889b5" - integrity sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w== - dependencies: - detect-newline "^3.0.0" - -jest-each@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-26.6.2.tgz#02526438a77a67401c8a6382dfe5999952c167cb" - integrity sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A== - dependencies: - "@jest/types" "^26.6.2" - chalk "^4.0.0" - jest-get-type "^26.3.0" - jest-util "^26.6.2" - pretty-format "^26.6.2" - -jest-environment-jsdom@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz#78d09fe9cf019a357009b9b7e1f101d23bd1da3e" - integrity sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q== - dependencies: - "@jest/environment" "^26.6.2" - "@jest/fake-timers" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/node" "*" - jest-mock "^26.6.2" - jest-util "^26.6.2" - jsdom "^16.4.0" - -jest-environment-node@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-26.6.2.tgz#824e4c7fb4944646356f11ac75b229b0035f2b0c" - integrity sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag== - dependencies: - "@jest/environment" "^26.6.2" - "@jest/fake-timers" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/node" "*" - jest-mock "^26.6.2" - jest-util "^26.6.2" - -jest-get-type@^26.3.0: - version "26.3.0" - resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" - integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig== - -jest-haste-map@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa" - integrity sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w== - dependencies: - "@jest/types" "^26.6.2" - "@types/graceful-fs" "^4.1.2" - "@types/node" "*" - anymatch "^3.0.3" - fb-watchman "^2.0.0" - graceful-fs "^4.2.4" - jest-regex-util "^26.0.0" - jest-serializer "^26.6.2" - jest-util "^26.6.2" - jest-worker "^26.6.2" - micromatch "^4.0.2" - sane "^4.0.3" - walker "^1.0.7" - optionalDependencies: - fsevents "^2.1.2" - -jest-jasmine2@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz#adc3cf915deacb5212c93b9f3547cd12958f2edd" - integrity sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg== - dependencies: - "@babel/traverse" "^7.1.0" - "@jest/environment" "^26.6.2" - "@jest/source-map" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/node" "*" - chalk "^4.0.0" - co "^4.6.0" - expect "^26.6.2" - is-generator-fn "^2.0.0" - jest-each "^26.6.2" - jest-matcher-utils "^26.6.2" - jest-message-util "^26.6.2" - jest-runtime "^26.6.3" - jest-snapshot "^26.6.2" - jest-util "^26.6.2" - pretty-format "^26.6.2" - throat "^5.0.0" - -jest-leak-detector@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz#7717cf118b92238f2eba65054c8a0c9c653a91af" - integrity sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg== - dependencies: - jest-get-type "^26.3.0" - pretty-format "^26.6.2" - -jest-matcher-utils@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz#8e6fd6e863c8b2d31ac6472eeb237bc595e53e7a" - integrity sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw== - dependencies: - chalk "^4.0.0" - jest-diff "^26.6.2" - jest-get-type "^26.3.0" - pretty-format "^26.6.2" - -jest-message-util@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-26.6.2.tgz#58173744ad6fc0506b5d21150b9be56ef001ca07" - integrity sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA== - dependencies: - "@babel/code-frame" "^7.0.0" - "@jest/types" "^26.6.2" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.4" - micromatch "^4.0.2" - pretty-format "^26.6.2" - slash "^3.0.0" - stack-utils "^2.0.2" - -jest-mock@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-26.6.2.tgz#d6cb712b041ed47fe0d9b6fc3474bc6543feb302" - integrity sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew== - dependencies: - "@jest/types" "^26.6.2" - "@types/node" "*" - -jest-pnp-resolver@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" - integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== - -jest-regex-util@^26.0.0: - version "26.0.0" - resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28" - integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A== - -jest-resolve-dependencies@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz#6680859ee5d22ee5dcd961fe4871f59f4c784fb6" - integrity sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg== - dependencies: - "@jest/types" "^26.6.2" - jest-regex-util "^26.0.0" - jest-snapshot "^26.6.2" - -jest-resolve@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-26.6.2.tgz#a3ab1517217f469b504f1b56603c5bb541fbb507" - integrity sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ== - dependencies: - "@jest/types" "^26.6.2" - chalk "^4.0.0" - graceful-fs "^4.2.4" - jest-pnp-resolver "^1.2.2" - jest-util "^26.6.2" - read-pkg-up "^7.0.1" - resolve "^1.18.1" - slash "^3.0.0" - -jest-runner@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-26.6.3.tgz#2d1fed3d46e10f233fd1dbd3bfaa3fe8924be159" - integrity sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ== - dependencies: - "@jest/console" "^26.6.2" - "@jest/environment" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/node" "*" - chalk "^4.0.0" - emittery "^0.7.1" - exit "^0.1.2" - graceful-fs "^4.2.4" - jest-config "^26.6.3" - jest-docblock "^26.0.0" - jest-haste-map "^26.6.2" - jest-leak-detector "^26.6.2" - jest-message-util "^26.6.2" - jest-resolve "^26.6.2" - jest-runtime "^26.6.3" - jest-util "^26.6.2" - jest-worker "^26.6.2" - source-map-support "^0.5.6" - throat "^5.0.0" - -jest-runtime@^26.6.3: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-26.6.3.tgz#4f64efbcfac398331b74b4b3c82d27d401b8fa2b" - integrity sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw== - dependencies: - "@jest/console" "^26.6.2" - "@jest/environment" "^26.6.2" - "@jest/fake-timers" "^26.6.2" - "@jest/globals" "^26.6.2" - "@jest/source-map" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/yargs" "^15.0.0" - chalk "^4.0.0" - cjs-module-lexer "^0.6.0" - collect-v8-coverage "^1.0.0" - exit "^0.1.2" - glob "^7.1.3" - graceful-fs "^4.2.4" - jest-config "^26.6.3" - jest-haste-map "^26.6.2" - jest-message-util "^26.6.2" - jest-mock "^26.6.2" - jest-regex-util "^26.0.0" - jest-resolve "^26.6.2" - jest-snapshot "^26.6.2" - jest-util "^26.6.2" - jest-validate "^26.6.2" - slash "^3.0.0" - strip-bom "^4.0.0" - yargs "^15.4.1" - -jest-serializer@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-26.6.2.tgz#d139aafd46957d3a448f3a6cdabe2919ba0742d1" - integrity sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g== - dependencies: - "@types/node" "*" - graceful-fs "^4.2.4" - -jest-snapshot@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-26.6.2.tgz#f3b0af1acb223316850bd14e1beea9837fb39c84" - integrity sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og== - dependencies: - "@babel/types" "^7.0.0" - "@jest/types" "^26.6.2" - "@types/babel__traverse" "^7.0.4" - "@types/prettier" "^2.0.0" - chalk "^4.0.0" - expect "^26.6.2" - graceful-fs "^4.2.4" - jest-diff "^26.6.2" - jest-get-type "^26.3.0" - jest-haste-map "^26.6.2" - jest-matcher-utils "^26.6.2" - jest-message-util "^26.6.2" - jest-resolve "^26.6.2" - natural-compare "^1.4.0" - pretty-format "^26.6.2" - semver "^7.3.2" - -jest-util@^26.1.0, jest-util@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1" - integrity sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q== - dependencies: - "@jest/types" "^26.6.2" - "@types/node" "*" - chalk "^4.0.0" - graceful-fs "^4.2.4" - is-ci "^2.0.0" - micromatch "^4.0.2" - -jest-validate@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.6.2.tgz#23d380971587150467342911c3d7b4ac57ab20ec" - integrity sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ== - dependencies: - "@jest/types" "^26.6.2" - camelcase "^6.0.0" - chalk "^4.0.0" - jest-get-type "^26.3.0" - leven "^3.1.0" - pretty-format "^26.6.2" - -jest-watcher@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-26.6.2.tgz#a5b683b8f9d68dbcb1d7dae32172d2cca0592975" - integrity sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ== - dependencies: - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - jest-util "^26.6.2" - string-length "^4.0.1" - -jest-worker@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" - integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^7.0.0" - -jest-worker@^27.0.6: - version "27.2.4" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.2.4.tgz#881455df75e22e7726a53f43703ab74d6b36f82d" - integrity sha512-Zq9A2Pw59KkVjBBKD1i3iE2e22oSjXhUKKuAK1HGX8flGwkm6NMozyEYzKd41hXc64dbd/0eWFeEEuxqXyhM+g== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^8.0.0" - -jest@^26.6.0: - version "26.6.3" - resolved "https://registry.yarnpkg.com/jest/-/jest-26.6.3.tgz#40e8fdbe48f00dfa1f0ce8121ca74b88ac9148ef" - integrity sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q== - dependencies: - "@jest/core" "^26.6.3" - import-local "^3.0.2" - jest-cli "^26.6.3" - -joycon@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03" - integrity sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw== - -jpeg-js@^0.4.2: - version "0.4.3" - resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.3.tgz#6158e09f1983ad773813704be80680550eff977b" - integrity sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q== - -js-levenshtein@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" - integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g== - -js-tokens@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== - -js-yaml@^3.13.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== - dependencies: - argparse "^1.0.7" - esprima "^4.0.0" - -jsdom@^16.4.0: - version "16.7.0" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" - integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== - dependencies: - abab "^2.0.5" - acorn "^8.2.4" - acorn-globals "^6.0.0" - cssom "^0.4.4" - cssstyle "^2.3.0" - data-urls "^2.0.0" - decimal.js "^10.2.1" - domexception "^2.0.1" - escodegen "^2.0.0" - form-data "^3.0.0" - html-encoding-sniffer "^2.0.1" - http-proxy-agent "^4.0.1" - https-proxy-agent "^5.0.0" - is-potential-custom-element-name "^1.0.1" - nwsapi "^2.2.0" - parse5 "6.0.1" - saxes "^5.0.1" - symbol-tree "^3.2.4" - tough-cookie "^4.0.0" - w3c-hr-time "^1.0.2" - w3c-xmlserializer "^2.0.0" - webidl-conversions "^6.1.0" - whatwg-encoding "^1.0.5" - whatwg-mimetype "^2.3.0" - whatwg-url "^8.5.0" - ws "^7.4.6" - xml-name-validator "^3.0.0" - -jsesc@^2.5.1: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" - integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== - -json-parse-better-errors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" - integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== - -json-parse-even-better-errors@^2.3.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" - integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== - -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== - -json5@2.x, json5@^2.1.2: - version "2.2.0" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" - integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== - dependencies: - minimist "^1.2.5" - -jsonfile@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= - optionalDependencies: - graceful-fs "^4.1.6" - -jsonfile@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" - integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== - dependencies: - universalify "^2.0.0" - optionalDependencies: - graceful-fs "^4.1.6" - -jsonparse@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" - integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA= - -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= - dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= - dependencies: - is-buffer "^1.1.5" - -kind-of@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== - -kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - -kleur@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" - integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== - -leven@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580" - integrity sha1-wuep93IJTe6dNCAq6KzORoeHVYA= - -leven@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" - integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== - -levn@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= - dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" - -lines-and-columns@^1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" - integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= - -loader-runner@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" - integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw== - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -locate-path@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" - integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== - dependencies: - p-locate "^5.0.0" - -lodash.capitalize@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9" - integrity sha1-+CbJtOKoUR2E46yinbBeGk87cqk= - -lodash.escaperegexp@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347" - integrity sha1-ZHYsSGGAglGKw99Mz11YhtriA0c= - -lodash.isplainobject@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" - integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= - -lodash.isstring@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" - integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= - -lodash.map@^4.5.1: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3" - integrity sha1-dx7Hg540c9nEzeKLGTlMNWL09tM= - -lodash.uniqby@^4.7.0: - version "4.7.0" - resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302" - integrity sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI= - -lodash@4.x, lodash@^4.17.12, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - -log-symbols@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" - integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== - dependencies: - chalk "^4.1.0" - is-unicode-supported "^0.1.0" - -longest@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/longest/-/longest-2.0.1.tgz#781e183296aa94f6d4d916dc335d0d17aefa23f8" - integrity sha1-eB4YMpaqlPbU2RbcM10NF676I/g= - -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" - -make-dir@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" - integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== - dependencies: - semver "^6.0.0" - -make-error@1.x, make-error@^1.1.1: - version "1.3.6" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" - integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== - -makeerror@1.0.x: - version "1.0.11" - resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" - integrity sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw= - dependencies: - tmpl "1.0.x" - -map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= - -map-obj@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" - integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= - -map-obj@^4.0.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" - integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== - -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= - dependencies: - object-visit "^1.0.0" - -md5@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/md5/-/md5-2.3.0.tgz#c3da9a6aae3a30b46b7b0c349b87b110dc3bda4f" - integrity sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g== - dependencies: - charenc "0.0.2" - crypt "0.0.2" - is-buffer "~1.1.6" - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= - -memfs@^3.2.2: - version "3.3.0" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.3.0.tgz#4da2d1fc40a04b170a56622c7164c6be2c4cbef2" - integrity sha512-BEE62uMfKOavX3iG7GYX43QJ+hAeeWnwIAuJ/R6q96jaMtiLzhsxHJC8B1L7fK7Pt/vXDRwb3SG/yBpNGDPqzg== - dependencies: - fs-monkey "1.0.3" - -meow@^8.0.0: - version "8.1.2" - resolved "https://registry.yarnpkg.com/meow/-/meow-8.1.2.tgz#bcbe45bda0ee1729d350c03cffc8395a36c4e897" - integrity sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q== - dependencies: - "@types/minimist" "^1.2.0" - camelcase-keys "^6.2.2" - decamelize-keys "^1.1.0" - hard-rejection "^2.1.0" - minimist-options "4.1.0" - normalize-package-data "^3.0.0" - read-pkg-up "^7.0.1" - redent "^3.0.0" - trim-newlines "^3.0.0" - type-fest "^0.18.0" - yargs-parser "^20.2.3" - -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= - -merge-stream@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" - integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== - -merge@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/merge/-/merge-2.1.1.tgz#59ef4bf7e0b3e879186436e8481c06a6c162ca98" - integrity sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w== - -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= - -micromatch@^3.1.4: - version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" - -micromatch@^4.0.2: - version "4.0.4" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.4.tgz#896d519dfe9db25fce94ceb7a500919bf881ebf9" - integrity sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg== - dependencies: - braces "^3.0.1" - picomatch "^2.2.3" - -mime-db@1.50.0: - version "1.50.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.50.0.tgz#abd4ac94e98d3c0e185016c67ab45d5fde40c11f" - integrity sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A== - -mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.24: - version "2.1.33" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.33.tgz#1fa12a904472fafd068e48d9e8401f74d3f70edb" - integrity sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g== - dependencies: - mime-db "1.50.0" - -mime@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== - -mime@^2.4.6: - version "2.5.2" - resolved "https://registry.yarnpkg.com/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe" - integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg== - -mimic-fn@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" - integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== - -mimic-fn@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" - integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== - -min-indent@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" - integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== - -minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== - dependencies: - brace-expansion "^1.1.7" - -minimist-options@4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" - integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A== - dependencies: - arrify "^1.0.1" - is-plain-obj "^1.1.0" - kind-of "^6.0.3" - -minimist@1.2.5, minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" - integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== - -mixin-deep@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" - integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - -mkdirp@1.x: - version "1.0.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== - -mri@1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/mri/-/mri-1.1.4.tgz#7cb1dd1b9b40905f1fac053abe25b6720f44744a" - integrity sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w== - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= - -ms@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" - integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== - -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - -msw@latest: - version "0.35.0" - resolved "https://registry.yarnpkg.com/msw/-/msw-0.35.0.tgz#18a4ceb6c822ef226a30421d434413bc45030d38" - integrity sha512-V7A6PqaS31F1k//fPS0OnO7vllfaqBUFsMEu3IpYixyWpiUInfyglodnbXhhtDyytkQikpkPZv8TZi/CvZzv/w== - dependencies: - "@mswjs/cookies" "^0.1.6" - "@mswjs/interceptors" "^0.12.6" - "@open-draft/until" "^1.0.3" - "@types/cookie" "^0.4.1" - "@types/inquirer" "^7.3.3" - "@types/js-levenshtein" "^1.1.0" - chalk "^4.1.1" - chokidar "^3.4.2" - cookie "^0.4.1" - graphql "^15.5.1" - headers-utils "^3.0.2" - inquirer "^8.1.1" - is-node-process "^1.0.1" - js-levenshtein "^1.1.6" - node-fetch "^2.6.1" - node-match-path "^0.6.3" - statuses "^2.0.0" - strict-event-emitter "^0.2.0" - type-fest "^1.2.2" - yargs "^17.0.1" - -mustache@^4.1.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.2.0.tgz#e5892324d60a12ec9c2a73359edca52972bf6f64" - integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ== - -mute-stream@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" - integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= - -mute-stream@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" - integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== - -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" - integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -natural-compare@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" - integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= - -negotiator@0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" - integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== - -neo-async@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== - -nice-try@^1.0.4: - version "1.0.5" - resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" - integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== - -node-fetch@2.6.7: - version "2.6.7" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" - integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== - dependencies: - whatwg-url "^5.0.0" - -node-fetch@^2.6.1: - version "2.6.5" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.5.tgz#42735537d7f080a7e5f78b6c549b7146be1742fd" - integrity sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ== - dependencies: - whatwg-url "^5.0.0" - -node-int64@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" - integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs= - -node-match-path@^0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/node-match-path/-/node-match-path-0.6.3.tgz#55dd8443d547f066937a0752dce462ea7dc27551" - integrity sha512-fB1reOHKLRZCJMAka28hIxCwQLxGmd7WewOCBDYKpyA1KXi68A7vaGgdZAPhY2E6SXoYt3KqYCCvXLJ+O0Fu/Q== - -node-modules-regexp@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" - integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= - -node-notifier@^8.0.0: - version "8.0.2" - resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-8.0.2.tgz#f3167a38ef0d2c8a866a83e318c1ba0efeb702c5" - integrity sha512-oJP/9NAdd9+x2Q+rfphB2RJCHjod70RcRLjosiPMMu5gjIfwVnOUGq2nbTjTUbmy0DJ/tFIVT30+Qe3nzl4TJg== - dependencies: - growly "^1.3.0" - is-wsl "^2.2.0" - semver "^7.3.2" - shellwords "^0.1.1" - uuid "^8.3.0" - which "^2.0.2" - -node-releases@^1.1.77: - version "1.1.77" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.77.tgz#50b0cfede855dd374e7585bf228ff34e57c1c32e" - integrity sha512-rB1DUFUNAN4Gn9keO2K1efO35IDK7yKHCdCaIMvFO7yUYmmZYeDjnGKle26G4rwj+LKRQpjyUUvMkPglwGCYNQ== - -normalize-package-data@^2.5.0: - version "2.5.0" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" - integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== - dependencies: - hosted-git-info "^2.1.4" - resolve "^1.10.0" - semver "2 || 3 || 4 || 5" - validate-npm-package-license "^3.0.1" - -normalize-package-data@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-3.0.3.tgz#dbcc3e2da59509a0983422884cd172eefdfa525e" - integrity sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA== - dependencies: - hosted-git-info "^4.0.1" - is-core-module "^2.5.0" - semver "^7.3.4" - validate-npm-package-license "^3.0.1" - -normalize-path@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" - integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= - dependencies: - remove-trailing-separator "^1.0.1" - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -npm-run-path@^2.0.0: - version "2.0.2" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" - integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= - dependencies: - path-key "^2.0.0" - -npm-run-path@^4.0.0, npm-run-path@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea" - integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw== - dependencies: - path-key "^3.0.0" - -nwsapi@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7" - integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ== - -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= - dependencies: - isobject "^3.0.0" - -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= - dependencies: - isobject "^3.0.1" - -on-exit-leak-free@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-0.2.0.tgz#b39c9e3bf7690d890f4861558b0d7b90a442d209" - integrity sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg== - -on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= - dependencies: - ee-first "1.1.1" - -once@^1.3.0, once@^1.3.1, once@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= - dependencies: - wrappy "1" - -onetime@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" - integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ= - dependencies: - mimic-fn "^1.0.0" - -onetime@^5.1.0, onetime@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e" - integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg== - dependencies: - mimic-fn "^2.1.0" - -optionator@^0.8.1: - version "0.8.3" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" - integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== - dependencies: - deep-is "~0.1.3" - fast-levenshtein "~2.0.6" - levn "~0.3.0" - prelude-ls "~1.1.2" - type-check "~0.3.2" - word-wrap "~1.2.3" - -ora@^5.4.1: - version "5.4.1" - resolved "https://registry.yarnpkg.com/ora/-/ora-5.4.1.tgz#1b2678426af4ac4a509008e5e4ac9e9959db9e18" - integrity sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ== - dependencies: - bl "^4.1.0" - chalk "^4.1.0" - cli-cursor "^3.1.0" - cli-spinners "^2.5.0" - is-interactive "^1.0.0" - is-unicode-supported "^0.1.0" - log-symbols "^4.1.0" - strip-ansi "^6.0.0" - wcwidth "^1.0.1" - -os-tmpdir@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= - -outvariant@^1.2.0, outvariant@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/outvariant/-/outvariant-1.2.1.tgz#e630f6cdc1dbf398ed857e36f219de4a005ccd35" - integrity sha512-bcILvFkvpMXh66+Ubax/inxbKRyWTUiiFIW2DWkiS79wakrLGn3Ydy+GvukadiyfZjaL6C7YhIem4EZSM282wA== - -outvariant@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/outvariant/-/outvariant-1.3.0.tgz#c39723b1d2cba729c930b74bf962317a81b9b1c9" - integrity sha512-yeWM9k6UPfG/nzxdaPlJkB2p08hCg4xP6Lx99F+vP8YF7xyZVfTmJjrrNalkmzudD4WFvNLVudQikqUmF8zhVQ== - -p-each-series@^2.1.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a" - integrity sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA== - -p-finally@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" - integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-limit@^3.0.2, p-limit@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" - integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== - dependencies: - yocto-queue "^0.1.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-locate@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" - integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== - dependencies: - p-limit "^3.0.2" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -page-with@^0.4.1: - version "0.4.2" - resolved "https://registry.yarnpkg.com/page-with/-/page-with-0.4.2.tgz#ee78dc05e7fd12881809b828c55f219acded9de5" - integrity sha512-fRzeel8bJfXFXdawmwZkbg8pK/IROnbcfp7GDoSerpcCbLQr9hW+zZURAgccG4JwCO5q+K3wdv44VBxRYTb9Dg== - dependencies: - "@open-draft/until" "^1.0.3" - "@types/debug" "^4.1.5" - "@types/express" "^4.17.11" - "@types/mustache" "^4.1.1" - "@types/uuid" "^8.3.0" - debug "^4.3.1" - express "^4.17.1" - headers-utils "^3.0.2" - memfs "^3.2.2" - mustache "^4.1.0" - playwright "^1.12.1" - uuid "^8.3.2" - webpack "^5.38.1" - webpack-merge "^5.7.3" - -parent-module@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" - integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g== - dependencies: - callsites "^3.0.0" - -parse-json@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" - integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== - dependencies: - "@babel/code-frame" "^7.0.0" - error-ex "^1.3.1" - json-parse-even-better-errors "^2.3.0" - lines-and-columns "^1.1.6" - -parse-passwd@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" - integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY= - -parse5@6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" - integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== - -parseurl@~1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" - integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== - -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= - -path-key@^2.0.0, path-key@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" - integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= - -path-key@^3.0.0, path-key@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" - integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== - -path-parse@^1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= - -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - -pend@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" - integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= - -picocolors@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-0.2.1.tgz#570670f793646851d1ba135996962abad587859f" - integrity sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA== - -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: - version "2.3.0" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" - integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== - -pino-abstract-transport@^0.5.0, pino-abstract-transport@v0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-0.5.0.tgz#4b54348d8f73713bfd14e3dc44228739aa13d9c0" - integrity sha512-+KAgmVeqXYbTtU2FScx1XS3kNyfZ5TrXY07V96QnUSFqo2gAqlvmaxH67Lj7SWazqsMabf+58ctdTcBgnOLUOQ== - dependencies: - duplexify "^4.1.2" - split2 "^4.0.0" - -pino-pretty@^7.6.1: - version "7.6.1" - resolved "https://registry.yarnpkg.com/pino-pretty/-/pino-pretty-7.6.1.tgz#42d20611050ad80d619edaf132c6d81d40f81d98" - integrity sha512-H7N6ZYkiyrfwBGW9CSjx0uyO9Q2Lyt73881+OTYk8v3TiTdgN92QHrWlEq/LeWw5XtDP64jeSk3mnc6T+xX9/w== - dependencies: - args "^5.0.1" - colorette "^2.0.7" - dateformat "^4.6.3" - fast-safe-stringify "^2.0.7" - joycon "^3.1.1" - on-exit-leak-free "^0.2.0" - pino-abstract-transport "^0.5.0" - pump "^3.0.0" - readable-stream "^3.6.0" - rfdc "^1.3.0" - secure-json-parse "^2.4.0" - sonic-boom "^2.2.0" - strip-json-comments "^3.1.1" - -pino-std-serializers@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-4.0.0.tgz#1791ccd2539c091ae49ce9993205e2cd5dbba1e2" - integrity sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q== - -pino@^7.10.0: - version "7.11.0" - resolved "https://registry.yarnpkg.com/pino/-/pino-7.11.0.tgz#0f0ea5c4683dc91388081d44bff10c83125066f6" - integrity sha512-dMACeu63HtRLmCG8VKdy4cShCPKaYDR4youZqoSWLxl5Gu99HUw8bw75thbPv9Nip+H+QYX8o3ZJbTdVZZ2TVg== - dependencies: - atomic-sleep "^1.0.0" - fast-redact "^3.0.0" - on-exit-leak-free "^0.2.0" - pino-abstract-transport v0.5.0 - pino-std-serializers "^4.0.0" - process-warning "^1.0.0" - quick-format-unescaped "^4.0.3" - real-require "^0.1.0" - safe-stable-stringify "^2.1.0" - sonic-boom "^2.2.1" - thread-stream "^0.15.1" - -pirates@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87" - integrity sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA== - dependencies: - node-modules-regexp "^1.0.0" - -pkg-dir@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" - integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== - dependencies: - find-up "^4.0.0" - -playwright@^1.12.1: - version "1.15.2" - resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.15.2.tgz#b350056d1fffbe5de5b1bdaca6ab73e35758baad" - integrity sha512-+Z+7ckihyxR6rK5q8DWC6eUbKARfXpyxpjNcoJfgwSr64lAOzjhyFQiPC/JkdIqhsLgZjxpWfl1S7fLb+wPkgA== - dependencies: - commander "^6.1.0" - debug "^4.1.1" - extract-zip "^2.0.1" - https-proxy-agent "^5.0.0" - jpeg-js "^0.4.2" - mime "^2.4.6" - pngjs "^5.0.0" - progress "^2.0.3" - proper-lockfile "^4.1.1" - proxy-from-env "^1.1.0" - rimraf "^3.0.2" - stack-utils "^2.0.3" - ws "^7.4.6" - yazl "^2.5.1" - -pluralize@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" - integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== - -pngjs@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" - integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== - -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= - -prelude-ls@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" - integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= - -prettier@^2.2.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.4.1.tgz#671e11c89c14a4cfc876ce564106c4a6726c9f5c" - integrity sha512-9fbDAXSBcc6Bs1mZrDYb3XKzDLm4EXXL9sC1LqKP5rZkT6KRr/rf9amVUcODVXgguK/isJz0d0hP72WeaKWsvA== - -pretty-format@^26.0.0, pretty-format@^26.6.2: - version "26.6.2" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-26.6.2.tgz#e35c2705f14cb7fe2fe94fa078345b444120fc93" - integrity sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg== - dependencies: - "@jest/types" "^26.6.2" - ansi-regex "^5.0.0" - ansi-styles "^4.0.0" - react-is "^17.0.1" - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - -process-warning@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-1.0.0.tgz#980a0b25dc38cd6034181be4b7726d89066b4616" - integrity sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q== - -progress@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== - -prompts@^2.0.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.1.tgz#befd3b1195ba052f9fd2fde8a486c4e82ee77f61" - integrity sha512-EQyfIuO2hPDsX1L/blblV+H7I0knhgAd82cVneCwcdND9B8AuCDuRcBH6yIcG4dFzlOUqbazQqwGjx5xmsNLuQ== - dependencies: - kleur "^3.0.3" - sisteransi "^1.0.5" - -proper-lockfile@^4.1.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/proper-lockfile/-/proper-lockfile-4.1.2.tgz#c8b9de2af6b2f1601067f98e01ac66baa223141f" - integrity sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA== - dependencies: - graceful-fs "^4.2.4" - retry "^0.12.0" - signal-exit "^3.0.2" - -proxy-addr@~2.0.5: - version "2.0.7" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" - integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== - dependencies: - forwarded "0.2.0" - ipaddr.js "1.9.1" - -proxy-from-env@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" - integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== - -psl@^1.1.33: - version "1.8.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" - integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== - -pump@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" - integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== - dependencies: - end-of-stream "^1.1.0" - once "^1.3.1" - -punycode@^2.1.0, punycode@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - -q@^1.5.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" - integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= - -qs@6.7.0: - version "6.7.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" - integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== - -quick-format-unescaped@^4.0.3: - version "4.0.4" - resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" - integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== - -quick-lru@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-4.0.1.tgz#5b8878f113a58217848c6482026c73e1ba57727f" - integrity sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g== - -randombytes@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - -range-parser@~1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" - integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== - -raw-body@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" - integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== - dependencies: - bytes "3.1.0" - http-errors "1.7.2" - iconv-lite "0.4.24" - unpipe "1.0.0" - -rc@^1.2.8: - version "1.2.8" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" - integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== - dependencies: - deep-extend "^0.6.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - -react-is@^17.0.1: - version "17.0.2" - resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" - integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== - -read-pkg-up@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" - integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== - dependencies: - find-up "^4.1.0" - read-pkg "^5.2.0" - type-fest "^0.8.1" - -read-pkg@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" - integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== - dependencies: - "@types/normalize-package-data" "^2.4.0" - normalize-package-data "^2.5.0" - parse-json "^5.0.0" - type-fest "^0.6.0" - -readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== - dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" - -readable-stream@^2.0.2, readable-stream@~2.3.6: - version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -real-require@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.1.0.tgz#736ac214caa20632847b7ca8c1056a0767df9381" - integrity sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg== - -redent@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/redent/-/redent-3.0.0.tgz#e557b7998316bb53c9f1f56fa626352c6963059f" - integrity sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg== - dependencies: - indent-string "^4.0.0" - strip-indent "^3.0.0" - -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - -registry-auth-token@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-4.2.1.tgz#6d7b4006441918972ccd5fedcd41dc322c79b250" - integrity sha512-6gkSb4U6aWJB4SF2ZvLb76yCBjcvufXBqvvEx1HbmKPkutswjW1xNVRY0+daljIYRbogN7O0etYSlbiaEQyMyw== - dependencies: - rc "^1.2.8" - -remove-trailing-separator@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" - integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= - -repeat-element@^1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9" - integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ== - -repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= - -require-main-filename@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" - integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== - -resolve-cwd@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" - integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== - dependencies: - resolve-from "^5.0.0" - -resolve-dir@^1.0.0, resolve-dir@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" - integrity sha1-eaQGRMNivoLybv/nOcm7U4IEb0M= - dependencies: - expand-tilde "^2.0.0" - global-modules "^1.0.0" - -resolve-from@5.0.0, resolve-from@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" - integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== - -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== - -resolve-global@1.0.0, resolve-global@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/resolve-global/-/resolve-global-1.0.0.tgz#a2a79df4af2ca3f49bf77ef9ddacd322dad19255" - integrity sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw== - dependencies: - global-dirs "^0.1.1" - -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= - -resolve@^1.10.0, resolve@^1.18.1: - version "1.20.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" - integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A== - dependencies: - is-core-module "^2.2.0" - path-parse "^1.0.6" - -restore-cursor@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" - integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368= - dependencies: - onetime "^2.0.0" - signal-exit "^3.0.2" - -restore-cursor@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" - integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA== - dependencies: - onetime "^5.1.0" - signal-exit "^3.0.2" - -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== - -retry@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" - integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= - -rfdc@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" - integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== - -rimraf@^3.0.0, rimraf@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" - integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== - dependencies: - glob "^7.1.3" - -rsvp@^4.8.4: - version "4.8.5" - resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" - integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== - -run-async@^2.2.0, run-async@^2.4.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" - integrity sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ== - -rxjs@^6.4.0: - version "6.6.7" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" - integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== - dependencies: - tslib "^1.9.0" - -rxjs@^7.2.0: - version "7.3.1" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.3.1.tgz#cc375521f9e238b474fe552b0b9fd1be33d08099" - integrity sha512-vNenx7gqjPyeKpRnM6S5Ksm/oFTRijWWzYlRON04KaehZ3YjDwEmVjGUGo0TKWVjeNXOujVRlh0K1drUbcdPkw== - dependencies: - tslib "~2.1.0" - -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.2" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" - integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== - -safe-buffer@^5.1.0, safe-buffer@~5.2.0: - version "5.2.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" - integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== - -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= - dependencies: - ret "~0.1.10" - -safe-stable-stringify@^2.1.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.3.1.tgz#ab67cbe1fe7d40603ca641c5e765cb942d04fc73" - integrity sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg== - -"safer-buffer@>= 2.1.2 < 3": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== - -sane@^4.0.3: - version "4.1.0" - resolved "https://registry.yarnpkg.com/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded" - integrity sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA== - dependencies: - "@cnakazawa/watch" "^1.0.3" - anymatch "^2.0.0" - capture-exit "^2.0.0" - exec-sh "^0.3.2" - execa "^1.0.0" - fb-watchman "^2.0.0" - micromatch "^3.1.4" - minimist "^1.1.1" - walker "~1.0.5" - -saxes@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" - integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== - dependencies: - xmlchars "^2.2.0" - -schema-utils@^3.1.0, schema-utils@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.1.1.tgz#bc74c4b6b6995c1d88f76a8b77bea7219e0c8281" - integrity sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw== - dependencies: - "@types/json-schema" "^7.0.8" - ajv "^6.12.5" - ajv-keywords "^3.5.2" - -secure-json-parse@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/secure-json-parse/-/secure-json-parse-2.4.0.tgz#5aaeaaef85c7a417f76271a4f5b0cc3315ddca85" - integrity sha512-Q5Z/97nbON5t/L/sH6mY2EacfjVGwrCcSi5D3btRO2GZ8pf1K1UN7Z9H5J57hjVU2Qzxr1xO+FmBhOvEkzCMmg== - -"semver@2 || 3 || 4 || 5", semver@^5.5.0: - version "5.7.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" - integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== - -semver@7.3.5, semver@7.x, semver@^7.3.2, semver@^7.3.4: - version "7.3.5" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" - integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ== - dependencies: - lru-cache "^6.0.0" - -semver@^6.0.0, semver@^6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - -semver@^7.3.7: - version "7.3.7" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" - integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== - dependencies: - lru-cache "^6.0.0" - -send@0.17.1: - version "0.17.1" - resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" - integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== - dependencies: - debug "2.6.9" - depd "~1.1.2" - destroy "~1.0.4" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "~1.7.2" - mime "1.6.0" - ms "2.1.1" - on-finished "~2.3.0" - range-parser "~1.2.1" - statuses "~1.5.0" - -serialize-javascript@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" - integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== - dependencies: - randombytes "^2.1.0" - -serve-static@1.14.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" - integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.17.1" - -set-blocking@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= - -set-cookie-parser@^2.4.6: - version "2.4.8" - resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.4.8.tgz#d0da0ed388bc8f24e706a391f9c9e252a13c58b2" - integrity sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg== - -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - -setprototypeof@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" - integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== - -shallow-clone@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" - integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== - dependencies: - kind-of "^6.0.2" - -shebang-command@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" - integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= - dependencies: - shebang-regex "^1.0.0" - -shebang-command@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" - integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== - dependencies: - shebang-regex "^3.0.0" - -shebang-regex@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" - integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= - -shebang-regex@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" - integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== - -shellwords@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" - integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== - -signal-exit@^3.0.0, signal-exit@^3.0.2: - version "3.0.5" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.5.tgz#9e3e8cc0c75a99472b44321033a7702e7738252f" - integrity sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ== - -signal-exit@^3.0.3: - version "3.0.6" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.6.tgz#24e630c4b0f03fea446a2bd299e62b4a6ca8d0af" - integrity sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ== - -simple-git-hooks@^2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/simple-git-hooks/-/simple-git-hooks-2.7.0.tgz#121a5c3023663b8abcc5648c8bfe8619dc263705" - integrity sha512-nQe6ASMO9zn5/htIrU37xEIHGr9E6wikXelLbOeTcfsX2O++DHaVug7RSQoq+kO7DvZTH37WA5gW49hN9HTDmQ== - -sisteransi@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" - integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== - -slash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" - integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== - -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - -sonic-boom@^2.2.0, sonic-boom@^2.2.1: - version "2.8.0" - resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-2.8.0.tgz#c1def62a77425090e6ad7516aad8eb402e047611" - integrity sha512-kuonw1YOYYNOve5iHdSahXPOK49GqwA+LZhI6Wz/l0rP57iKyXXIHaRagOBHAPmGwJC6od2Z9zgvZ5loSgMlVg== - dependencies: - atomic-sleep "^1.0.0" - -source-map-resolve@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" - integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== - dependencies: - atob "^2.1.2" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - -source-map-support@^0.5.17, source-map-support@^0.5.6, source-map-support@~0.5.20: - version "0.5.20" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9" - integrity sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw== - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - -source-map-url@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" - integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== - -source-map@^0.5.0, source-map@^0.5.6: - version "0.5.7" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= - -source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - -source-map@^0.7.3, source-map@~0.7.2: - version "0.7.3" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" - integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== - -spawn-error-forwarder@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/spawn-error-forwarder/-/spawn-error-forwarder-1.0.0.tgz#1afd94738e999b0346d7b9fc373be55e07577029" - integrity sha1-Gv2Uc46ZmwNG17n8NzvlXgdXcCk= - -spdx-correct@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" - integrity sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w== - dependencies: - spdx-expression-parse "^3.0.0" - spdx-license-ids "^3.0.0" - -spdx-exceptions@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d" - integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A== - -spdx-expression-parse@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" - integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== - dependencies: - spdx-exceptions "^2.1.0" - spdx-license-ids "^3.0.0" - -spdx-license-ids@^3.0.0: - version "3.0.10" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.10.tgz#0d9becccde7003d6c658d487dd48a32f0bf3014b" - integrity sha512-oie3/+gKf7QtpitB0LYLETe+k8SifzsX4KixvpOsbI6S0kRiRQ5MKOio8eMSAKQ17N06+wdEOXRiId+zOxo0hA== - -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== - dependencies: - extend-shallow "^3.0.0" - -split2@^3.0.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/split2/-/split2-3.2.2.tgz#bf2cf2a37d838312c249c89206fd7a17dd12365f" - integrity sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg== - dependencies: - readable-stream "^3.0.0" - -split2@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/split2/-/split2-4.1.0.tgz#101907a24370f85bb782f08adaabe4e281ecf809" - integrity sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ== - -split2@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/split2/-/split2-1.0.0.tgz#52e2e221d88c75f9a73f90556e263ff96772b314" - integrity sha1-UuLiIdiMdfmnP5BVbiY/+WdysxQ= - dependencies: - through2 "~2.0.0" - -sprintf-js@~1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= - -stack-utils@^2.0.2, stack-utils@^2.0.3: - version "2.0.5" - resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" - integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== - dependencies: - escape-string-regexp "^2.0.0" - -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - -"statuses@>= 1.5.0 < 2", statuses@~1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= - -statuses@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" - integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== - -stream-combiner2@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/stream-combiner2/-/stream-combiner2-1.1.1.tgz#fb4d8a1420ea362764e21ad4780397bebcb41cbe" - integrity sha1-+02KFCDqNidk4hrUeAOXvry0HL4= - dependencies: - duplexer2 "~0.1.0" - readable-stream "^2.0.2" - -stream-shift@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" - integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== - -strict-event-emitter@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/strict-event-emitter/-/strict-event-emitter-0.2.0.tgz#78e2f75dc6ea502e5d8a877661065a1e2deedecd" - integrity sha512-zv7K2egoKwkQkZGEaH8m+i2D0XiKzx5jNsiSul6ja2IYFvil10A59Z9Y7PPAAe5OW53dQUf9CfsHKzjZzKkm1w== - dependencies: - events "^3.3.0" - -string-length@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a" - integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ== - dependencies: - char-regex "^1.0.2" - strip-ansi "^6.0.0" - -string-width@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" - integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== - dependencies: - is-fullwidth-code-point "^2.0.0" - strip-ansi "^4.0.0" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string_decoder@^1.1.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" - integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== - dependencies: - safe-buffer "~5.2.0" - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -strip-ansi@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" - integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= - dependencies: - ansi-regex "^3.0.0" - -strip-ansi@^5.1.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== - dependencies: - ansi-regex "^4.1.0" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -strip-bom@4.0.0, strip-bom@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" - integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== - -strip-eof@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= - -strip-final-newline@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" - integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== - -strip-indent@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" - integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== - dependencies: - min-indent "^1.0.0" - -strip-json-comments@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.0.1.tgz#85713975a91fb87bf1b305cca77395e40d2a64a7" - integrity sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw== - -strip-json-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" - integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== - -strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - -supports-color@^5.3.0: - version "5.5.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" - integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== - dependencies: - has-flag "^3.0.0" - -supports-color@^7.0.0, supports-color@^7.1.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - -supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - -supports-hyperlinks@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb" - integrity sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ== - dependencies: - has-flag "^4.0.0" - supports-color "^7.0.0" - -symbol-tree@^3.2.4: - version "3.2.4" - resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" - integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== - -tapable@^2.1.1, tapable@^2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" - integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== - -terminal-link@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994" - integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ== - dependencies: - ansi-escapes "^4.2.1" - supports-hyperlinks "^2.0.0" - -terser-webpack-plugin@^5.1.3: - version "5.2.4" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.2.4.tgz#ad1be7639b1cbe3ea49fab995cbe7224b31747a1" - integrity sha512-E2CkNMN+1cho04YpdANyRrn8CyN4yMy+WdFKZIySFZrGXZxJwJP6PMNGGc/Mcr6qygQHUUqRxnAPmi0M9f00XA== - dependencies: - jest-worker "^27.0.6" - p-limit "^3.1.0" - schema-utils "^3.1.1" - serialize-javascript "^6.0.0" - source-map "^0.6.1" - terser "^5.7.2" - -terser@^5.7.2: - version "5.9.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.9.0.tgz#47d6e629a522963240f2b55fcaa3c99083d2c351" - integrity sha512-h5hxa23sCdpzcye/7b8YqbE5OwKca/ni0RQz1uRX3tGh8haaGHqcuSqbGRybuAKNdntZ0mDgFNXPJ48xQ2RXKQ== - dependencies: - commander "^2.20.0" - source-map "~0.7.2" - source-map-support "~0.5.20" - -test-exclude@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" - integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w== - dependencies: - "@istanbuljs/schema" "^0.1.2" - glob "^7.1.4" - minimatch "^3.0.4" - -text-extensions@^1.0.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/text-extensions/-/text-extensions-1.9.0.tgz#1853e45fee39c945ce6f6c36b2d659b5aabc2a26" - integrity sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ== - -thread-stream@^0.15.1: - version "0.15.2" - resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-0.15.2.tgz#fb95ad87d2f1e28f07116eb23d85aba3bc0425f4" - integrity sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA== - dependencies: - real-require "^0.1.0" - -throat@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" - integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== - -through2@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/through2/-/through2-4.0.2.tgz#a7ce3ac2a7a8b0b966c80e7c49f0484c3b239764" - integrity sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw== - dependencies: - readable-stream "3" - -through2@~2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" - integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== - dependencies: - readable-stream "~2.3.6" - xtend "~4.0.1" - -"through@>=2.2.7 <3", through@^2.3.6: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= - -tmp@^0.0.33: - version "0.0.33" - resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" - integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw== - dependencies: - os-tmpdir "~1.0.2" - -tmpl@1.0.x: - version "1.0.5" - resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" - integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== - -to-fast-properties@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" - integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= - -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= - dependencies: - kind-of "^3.0.2" - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" - -toidentifier@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" - integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== - -tough-cookie@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" - integrity sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg== - dependencies: - psl "^1.1.33" - punycode "^2.1.1" - universalify "^0.1.2" - -tr46@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240" - integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw== - dependencies: - punycode "^2.1.1" - -tr46@~0.0.3: - version "0.0.3" - resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" - integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= - -traverse@~0.6.6: - version "0.6.6" - resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137" - integrity sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc= - -trim-newlines@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" - integrity sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw== - -ts-jest@^26.5.5: - version "26.5.6" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-26.5.6.tgz#c32e0746425274e1dfe333f43cd3c800e014ec35" - integrity sha512-rua+rCP8DxpA8b4DQD/6X2HQS8Zy/xzViVYfEs2OQu68tkCuKLV0Md8pmX55+W24uRIyAsf/BajRfxOs+R2MKA== - dependencies: - bs-logger "0.x" - buffer-from "1.x" - fast-json-stable-stringify "2.x" - jest-util "^26.1.0" - json5 "2.x" - lodash "4.x" - make-error "1.x" - mkdirp "1.x" - semver "7.x" - yargs-parser "20.x" - -ts-node@^10.4.0: - version "10.4.0" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.4.0.tgz#680f88945885f4e6cf450e7f0d6223dd404895f7" - integrity sha512-g0FlPvvCXSIO1JDF6S232P5jPYqBkRL9qly81ZgAOSU7rwI0stphCgd2kLiCrU9DjQCrJMWEqcNSjQL02s6d8A== - dependencies: - "@cspotcode/source-map-support" "0.7.0" - "@tsconfig/node10" "^1.0.7" - "@tsconfig/node12" "^1.0.7" - "@tsconfig/node14" "^1.0.0" - "@tsconfig/node16" "^1.0.2" - acorn "^8.4.1" - acorn-walk "^8.1.1" - arg "^4.1.0" - create-require "^1.1.0" - diff "^4.0.1" - make-error "^1.1.1" - yn "3.1.1" - -ts-node@^9.1.1: - version "9.1.1" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-9.1.1.tgz#51a9a450a3e959401bda5f004a72d54b936d376d" - integrity sha512-hPlt7ZACERQGf03M253ytLY3dHbGNGrAq9qIHWUY9XHYl1z7wYngSr3OQ5xmui8o2AaxsONxIzjafLUiWBo1Fg== - dependencies: - arg "^4.1.0" - create-require "^1.1.0" - diff "^4.0.1" - make-error "^1.1.1" - source-map-support "^0.5.17" - yn "3.1.1" - -tslib@^1.9.0: - version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - -tslib@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" - integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== - -type-check@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" - integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= - dependencies: - prelude-ls "~1.1.2" - -type-detect@4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" - integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== - -type-fest@^0.18.0: - version "0.18.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" - integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== - -type-fest@^0.21.3: - version "0.21.3" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" - integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== - -type-fest@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" - integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== - -type-fest@^0.8.1: - version "0.8.1" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" - integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== - -type-fest@^1.2.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1" - integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== - -type-is@~1.6.17, type-is@~1.6.18: - version "1.6.18" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" - integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== - dependencies: - media-typer "0.3.0" - mime-types "~2.1.24" - -typedarray-to-buffer@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" - integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q== - dependencies: - is-typedarray "^1.0.0" - -typescript@4.3.5: - version "4.3.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4" - integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA== - -typescript@^4.4.3: - version "4.5.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.4.tgz#a17d3a0263bf5c8723b9c52f43c5084edf13c2e8" - integrity sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg== - -union-value@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" - integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" - -universalify@^0.1.0, universalify@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" - integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== - -universalify@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717" - integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ== - -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= - -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= - dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - -uri-js@^4.2.2: - version "4.4.1" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" - integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== - dependencies: - punycode "^2.1.0" - -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= - -use@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" - integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== - -util-deprecate@^1.0.1, util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= - -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= - -uuid@^8.3.0, uuid@^8.3.1, uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - -v8-to-istanbul@^7.0.0: - version "7.1.2" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz#30898d1a7fa0c84d225a2c1434fb958f290883c1" - integrity sha512-TxNb7YEUwkLXCQYeudi6lgQ/SZrzNO4kMdlqVxaZPUIUjCv6iSSypUQX70kNBSERpQ8fk48+d61FXk+tgqcWow== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.1" - convert-source-map "^1.6.0" - source-map "^0.7.3" - -validate-npm-package-license@^3.0.1: - version "3.0.4" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" - integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== - dependencies: - spdx-correct "^3.0.0" - spdx-expression-parse "^3.0.0" - -vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= - -w3c-hr-time@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd" - integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ== - dependencies: - browser-process-hrtime "^1.0.0" - -w3c-xmlserializer@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a" - integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA== - dependencies: - xml-name-validator "^3.0.0" - -walker@^1.0.7, walker@~1.0.5: - version "1.0.7" - resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" - integrity sha1-L3+bj9ENZ3JisYqITijRlhjgKPs= - dependencies: - makeerror "1.0.x" - -watchpack@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.2.0.tgz#47d78f5415fe550ecd740f99fe2882323a58b1ce" - integrity sha512-up4YAn/XHgZHIxFBVCdlMiWDj6WaLKpwVeGQk2I5thdYxF/KmF0aaz6TfJZ/hfl1h/XlcDr7k1KH7ThDagpFaA== - dependencies: - glob-to-regexp "^0.4.1" - graceful-fs "^4.1.2" - -wcwidth@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" - integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= - dependencies: - defaults "^1.0.3" - -webidl-conversions@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" - integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= - -webidl-conversions@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff" - integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA== - -webidl-conversions@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514" - integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w== - -webpack-merge@^5.7.3: - version "5.8.0" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.8.0.tgz#2b39dbf22af87776ad744c390223731d30a68f61" - integrity sha512-/SaI7xY0831XwP6kzuwhKWVKDP9t1QY1h65lAFLbZqMPIuYcD9QAW4u9STIbU9kaJbPBB/geU/gLr1wDjOhQ+Q== - dependencies: - clone-deep "^4.0.1" - wildcard "^2.0.0" - -webpack-sources@^3.2.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.1.tgz#251a7d9720d75ada1469ca07dbb62f3641a05b6d" - integrity sha512-t6BMVLQ0AkjBOoRTZgqrWm7xbXMBzD+XDq2EZ96+vMfn3qKgsvdXZhbPZ4ElUOpdv4u+iiGe+w3+J75iy/bYGA== - -webpack@^5.38.1: - version "5.57.1" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.57.1.tgz#ead5ace2c17ecef2ae8126f143bfeaa7f55eab44" - integrity sha512-kHszukYjTPVfCOEyrUthA3jqJwduY/P3eO8I0gMNOZGIQWKAwZftxmp5hq6paophvwo9NoUrcZOecs9ulOyyTg== - dependencies: - "@types/eslint-scope" "^3.7.0" - "@types/estree" "^0.0.50" - "@webassemblyjs/ast" "1.11.1" - "@webassemblyjs/wasm-edit" "1.11.1" - "@webassemblyjs/wasm-parser" "1.11.1" - acorn "^8.4.1" - acorn-import-assertions "^1.7.6" - browserslist "^4.14.5" - chrome-trace-event "^1.0.2" - enhanced-resolve "^5.8.3" - es-module-lexer "^0.9.0" - eslint-scope "5.1.1" - events "^3.2.0" - glob-to-regexp "^0.4.1" - graceful-fs "^4.2.4" - json-parse-better-errors "^1.0.2" - loader-runner "^4.2.0" - mime-types "^2.1.27" - neo-async "^2.6.2" - schema-utils "^3.1.0" - tapable "^2.1.1" - terser-webpack-plugin "^5.1.3" - watchpack "^2.2.0" - webpack-sources "^3.2.0" - -whatwg-encoding@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" - integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw== - dependencies: - iconv-lite "0.4.24" - -whatwg-mimetype@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" - integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g== - -whatwg-url@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" - integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= - dependencies: - tr46 "~0.0.3" - webidl-conversions "^3.0.0" - -whatwg-url@^8.0.0, whatwg-url@^8.5.0: - version "8.7.0" - resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77" - integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg== - dependencies: - lodash "^4.7.0" - tr46 "^2.1.0" - webidl-conversions "^6.1.0" - -which-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= - -which@^1.2.14, which@^1.2.9: - version "1.3.1" - resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" - integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== - dependencies: - isexe "^2.0.0" - -which@^2.0.1, which@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" - integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== - dependencies: - isexe "^2.0.0" - -wildcard@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" - integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== - -word-wrap@^1.0.3, word-wrap@~1.2.3: - version "1.2.3" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" - integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== - -wrap-ansi@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" - integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= - -write-file-atomic@^3.0.0: - version "3.0.3" - resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8" - integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q== - dependencies: - imurmurhash "^0.1.4" - is-typedarray "^1.0.0" - signal-exit "^3.0.2" - typedarray-to-buffer "^3.1.5" - -ws@^7.4.6: - version "7.5.5" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.5.tgz#8b4bc4af518cfabd0473ae4f99144287b33eb881" - integrity sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w== - -xml-name-validator@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" - integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== - -xmlchars@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" - integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== - -xtend@~4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - -y18n@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" - integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - -yaml@^1.10.0: - version "1.10.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" - integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== - -yargs-parser@20.x, yargs-parser@^20.2.2, yargs-parser@^20.2.3: - version "20.2.9" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" - integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== - -yargs-parser@^18.1.2: - version "18.1.3" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" - integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs-parser@^21.0.0: - version "21.0.0" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.0.tgz#a485d3966be4317426dd56bdb6a30131b281dc55" - integrity sha512-z9kApYUOCwoeZ78rfRYYWdiU/iNL6mwwYlkkZfJoyMR1xps+NEBX5X7XmRpxkZHhXJ6+Ey00IwKxBBSW9FIjyA== - -yargs@^15.4.1: - version "15.4.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" - integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== - dependencies: - cliui "^6.0.0" - decamelize "^1.2.0" - find-up "^4.1.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^4.2.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^18.1.2" - -yargs@^17.0.0: - version "17.3.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.3.1.tgz#da56b28f32e2fd45aefb402ed9c26f42be4c07b9" - integrity sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.0.0" - -yargs@^17.0.1: - version "17.2.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.2.1.tgz#e2c95b9796a0e1f7f3bf4427863b42e0418191ea" - integrity sha512-XfR8du6ua4K6uLGm5S6fA+FIJom/MdJcFNVY8geLlp2v8GYbOXD4EB1tPNZsRn4vBzKGMgb5DRZMeWuFc2GO8Q== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" - -yargs@^17.4.1: - version "17.4.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.4.1.tgz#ebe23284207bb75cee7c408c33e722bfb27b5284" - integrity sha512-WSZD9jgobAg3ZKuCQZSa3g9QOJeCCqLoLAykiWgmXnDo9EPnn4RPf5qVTtzgOx66o6/oqhcA5tHtJXpG8pMt3g== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.0.0" - -yauzl@^2.10.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" - integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk= - dependencies: - buffer-crc32 "~0.2.3" - fd-slicer "~1.1.0" - -yazl@^2.5.1: - version "2.5.1" - resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.5.1.tgz#a3d65d3dd659a5b0937850e8609f22fffa2b5c35" - integrity sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw== - dependencies: - buffer-crc32 "~0.2.3" - -yn@3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" - integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== - -yocto-queue@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" - integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==