From e7fdec529d27d0376b577a56fd5c7b8717924397 Mon Sep 17 00:00:00 2001 From: Alexandre Bodin Date: Fri, 19 Apr 2024 10:21:07 +0200 Subject: [PATCH 1/5] chore: update codemod to match new format --- .../entity-service-document-service.code.ts | 73 +++++++++++++++++-- 1 file changed, 68 insertions(+), 5 deletions(-) diff --git a/packages/utils/upgrade/resources/codemods/5.0.0/entity-service-document-service.code.ts b/packages/utils/upgrade/resources/codemods/5.0.0/entity-service-document-service.code.ts index b7fef3fda1..f940091602 100644 --- a/packages/utils/upgrade/resources/codemods/5.0.0/entity-service-document-service.code.ts +++ b/packages/utils/upgrade/resources/codemods/5.0.0/entity-service-document-service.code.ts @@ -142,12 +142,39 @@ const objectParam_10 = [...objectParam_10_2]; strapi.entityService.findOne(...[...objectParam_10]); +Case: find, create, update, delete with entityId as first argument + +strapi.entityService.findMany(uid, { + fields: ["id", "name", "description"], + populate: ["author", "comments"], + publicationState: "preview", +}); + +strapi.entityService.create(uid, { + data: { + name: "John Doe", + age: 30, + }, +}); + +strapi.entityService.update(uid, entityId, { + data: { + name: "John Doe", + age: 30, + }, +}); + +strapi.entityService.delete(uid, entityId); +strapi.entityService.findOne(uid, entityId); + */ -const movedFunctions = ['findOne', 'find', 'count', 'create', 'update', 'delete']; +const movedFunctions = ['findOne', 'findMany', 'count', 'create', 'update', 'delete']; + +const functionsWithEntityId = ['findOne', 'update', 'delete']; const transformDeclaration = (path: ASTPath, name: any, j: JSCodeshift) => { - const declaration = findClosesDeclaration(path, name, j); + const declaration = findClosestDeclaration(path, name, j); if (!declaration) { return; @@ -222,7 +249,7 @@ const transformObjectParam = (path: ASTPath, expression: ObjectExpression, break; } case j.Identifier.check(prop.value): { - const declaration = findClosesDeclaration(path, prop.value.name, j); + const declaration = findClosestDeclaration(path, prop.value.name, j); if (!declaration) { return; @@ -253,7 +280,7 @@ const transformObjectParam = (path: ASTPath, expression: ObjectExpression, }); }; -const findClosesDeclaration = (path: ASTPath, name: string, j) => { +const findClosestDeclaration = (path: ASTPath, name: string, j) => { // find Identifier declaration const scope = path.scope.lookup(name); @@ -318,7 +345,7 @@ const transform: Transform = (file, api) => { case j.Identifier.check(arg.argument): { const identifier = arg.argument; - const declaration = findClosesDeclaration(path, identifier.name, j); + const declaration = findClosestDeclaration(path, identifier.name, j); if (!declaration) { return arg; @@ -351,6 +378,42 @@ const transform: Transform = (file, api) => { const [docUID, ...rest] = resolvedArgs; + // function with entityId as first argument + if ( + j.Identifier.check(path.value.callee.property) && + functionsWithEntityId.includes(path.value.callee.property.name) + ) { + rest.splice(0, 1); + + // in case no extra params are passed in the function e.g delete(uid, entityId) + if (rest.length === 0) { + rest.push( + j.objectExpression.from({ + properties: [], + }) + ); + } + + const params = rest[0]; + + const placeholder = j.objectProperty(j.identifier('documentId'), j.literal('__TODO__')); + + // add documentId to params with a placeholder + if (j.ObjectExpression.check(params)) { + params.properties.unshift(placeholder); + } else if (j.Identifier.check(params)) { + const declaration = findClosestDeclaration(path, params.name, j); + + if (!declaration) { + return; + } + + if (j.ObjectExpression.check(declaration.init)) { + declaration.init.properties.unshift(placeholder); + } + } + } + path.value.arguments.forEach((arg) => { transformElement(path, arg, j); }); From 2d8197cd02f55b217bab2358f0e3e1e45ee991da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Herbaux?= Date: Fri, 19 Apr 2024 14:33:41 +0200 Subject: [PATCH 2/5] docs: contributor guide for type system concepts (#20120) Co-authored-by: Ben Irvin Co-authored-by: Hannah Paine <151527179+hanpaine@users.noreply.github.com> --- .../05-type-system/02-concepts/01-schema.mdx | 141 +++ .../05-type-system/02-concepts/02-uid.mdx | 188 ++++ .../02-concepts/03-public-registry.mdx | 316 ++++++ .../05-type-system/02-concepts/index.md | 15 + docs/docusaurus.config.js | 4 + docs/package.json | 1 + docs/yarn.lock | 922 +++++++++++++++++- 7 files changed, 1578 insertions(+), 9 deletions(-) create mode 100644 docs/docs/guides/05-type-system/02-concepts/01-schema.mdx create mode 100644 docs/docs/guides/05-type-system/02-concepts/02-uid.mdx create mode 100644 docs/docs/guides/05-type-system/02-concepts/03-public-registry.mdx create mode 100644 docs/docs/guides/05-type-system/02-concepts/index.md diff --git a/docs/docs/guides/05-type-system/02-concepts/01-schema.mdx b/docs/docs/guides/05-type-system/02-concepts/01-schema.mdx new file mode 100644 index 0000000000..115ad857ca --- /dev/null +++ b/docs/docs/guides/05-type-system/02-concepts/01-schema.mdx @@ -0,0 +1,141 @@ +--- +title: Schema +tags: + - typescript + - type system + - type + - concepts +--- + +The schema is the primary data structure leveraged within the Strapi Type System, defining how content is structured and managed in the application. + +It serves several key functions: + +- **Representation**: At its core, a schema outlines and defines the structure of Strapi content. This is useful when dealing with features that need access to low level schema properties (_e.g. attributes, plugin options, etc..._). + +- **Inference**: The schema allows inferring and configuring numerous other types. This includes entities like `ContentType` or `Component`, among others. + +### Scope + +Schema types represent **loaded** schemas in the context of a Strapi server application and should be used accordingly. + +:::caution +Database models and raw schema definitions (_aka schemas before being loaded by the Strapi server_) are **not** the same types and can't be used interchangeably. +::: + +### Sub-Types + +Each box is a type that extends the base Schema interface. + +In between each box is text that represents the discriminant used to differentiate the subtype from others. + +```mermaid +flowchart TB; + Schema -- "modelType: contentType" ---- ContentTypeSchema + Schema -- "modelType: component" ---- ComponentSchema + ContentTypeSchema -- "kind: collectionType" ---- CollectionTypeSchema + ContentTypeSchema -- "kind: singleType" ---- SingleTypeSchema +``` + +### Properties + +Schema types contain useful information that helps other types know how to interact with the Strapi content. + +This is facilitated through multiple properties. + +#### Options + +A set of properties used to configure the schema. It contains information on features activation among other things. + +This can be really useful to make the types adapt to a given schema. + +For instance, the document service uses the `options.draftAndPublish` property to determine whether it should add publication methods to the service type. + +#### Plugin Options + +These options provide the ability to alter or enhance the behaviour of the system based on specific values. + +If a plugin is enabled, it might bring functionality that can affect how types interact with each other. + +For example, it's possible to add or remove certain entity-service filters from the query type based on whether a plugin is enabled. + +#### Attributes + +Strongly typed schema attributes allows the Type System to infer actual entities types based on their properties. + +For instance, a string attribute will resolve to a primitive string in an entity, whereas a repeatable component attribute will resolve to an array of objects. + +### Usage + +import Tabs from '@theme/Tabs' +import TabItem from '@theme/TabItem' + + + +When designing public APIs (and in most other scenarios), it's advised to use the high-level schema types found in the `Schema` namespace. + +Schema definitions exported from the `Schema` namespace are targeting the dynamic types found in the public schema registries, and will dynamically adapt to the current context while extending the base Schema types. + +:::info +If the public registries are empty (_e.g. types are not generated yet, not in the context of a Strapi application, ..._), schema types will fallback to their low-level definitions. +::: + +```typescript +import type { Schema } from '@strapi/strapi'; + +declare const schema: Schema.Schema; +declare const contentType: Schema.ContentType; +declare const component: Schema.Component; + +declare function processAnySchema(schema: Schema.Schema): void; + +processAnySchema(schema); // ✅ +processAnySchema(contentType); // ✅ +processAnySchema(component); // ✅ + +declare function processContentTypeSchema(schema: Schema.ContentType): void; + +processContentTypeSchema(schema); // ✅ +processContentTypeSchema(contentType); // ✅ +processContentTypeSchema(component); // ❌ Error, a component schema is not assignable to a content-type schema + +declare function processComponentSchema(schema: Schema.Component): void; + +processComponentSchema(schema); // ✅ +processComponentSchema(contentType); // ❌ Error, a content-type schema is not assignable to a component schema +processComponentSchema(component); // ✅ +``` + + +Schema definitions exported from the `Struct` namespace defines the low level type representation of Strapi schemas. + +:::caution +Those types can be useful when you want to validate other types against the base ones, but realistically, the public Schema types should almost always be preferred. +::: +```typescript +import type { Struct } from '@strapi/strapi'; + +declare const schema: Struct.Schema; +declare const contentType: Struct.ContentTypeSchema; +declare const component: Struct.ComponentSchema; + +declare function processAnySchema(schema: Struct.Schema): void; + +processAnySchema(schema); // ✅ +processAnySchema(contentType); // ✅ +processAnySchema(component); // ✅ + +declare function processContentTypeSchema(schema: Struct.ContentTypeSchema): void; + +processContentTypeSchema(schema); // ✅ +processContentTypeSchema(contentType); // ✅ +processContentTypeSchema(component); // ❌ Error, a component schema is not assignable to a content-type schema + +declare function processComponentSchema(schema: Struct.ComponentSchema): void; + +processComponentSchema(schema); // ✅ +processComponentSchema(contentType); // ❌ Error, a content-type schema is not assignable to a component schema +processComponentSchema(component); // ✅ +``` + + diff --git a/docs/docs/guides/05-type-system/02-concepts/02-uid.mdx b/docs/docs/guides/05-type-system/02-concepts/02-uid.mdx new file mode 100644 index 0000000000..db2692423d --- /dev/null +++ b/docs/docs/guides/05-type-system/02-concepts/02-uid.mdx @@ -0,0 +1,188 @@ +--- +title: UID +tags: + - typescript + - type system + - type + - concepts +toc_max_heading_level: 4 +--- + +:::note +On this page, **a resource** is considered as **anything that can be identified by a UID**. + +This includes (but is not limited to) controllers, schema, services, policies, middlewares, etc... +::: + + +In the Type System, UIDs play a crucial role in referencing various resources (such as schema and entities) by attaching a unique identifier. + +To put it simply, a UID is a unique (string) literal key used to identify, locate, or access a particular resource within the system. + +:::tip +This makes it the perfect tool to index type registries or to use as a type parameter for resource-centric types. +::: + +### Format + +A UID is composed of 3 different parts: +1. A namespace ([link](#1-namespaces)) +2. A separator ([link](#2-separators)) +3. A name ([link](#3-names)) + +#### 1. Namespaces + +There are two main families of namespaces: + +- Scoped (_aka parametrized_) +- Non-scoped (_aka constants_) + +A third kind exists for component UIDs and is defined only by a dynamic category: ``. + +##### Scoped + +Scoped namespaces are defined by a base name, followed by a separator (`::`) and any string. + +In Strapi there are two of them: + +| Name | Definition | Description | +|--------|:-----------------:|------------------------------------------------------| +| API | `api::` | Represent a resource present in the `` API | +| Plugin | `plugin::` | Represent a resource present in the `` plugin | + +##### Non-Scoped + +These namespaces are used as a simple prefix and define the origin of a resource. + +Strapi uses three of them to create UIDs + +| Name | Definition | Description | +|--------|:----------:|-------------------------------------------------------------------------------| +| Strapi | `strapi` | Represent a resource present in the core of strapi | +| Admin | `admin` | Represent a resource present in Strapi admin | +| Global | `global` | Rarely used (_e.g. policies or middlewares_), it represents a global resource | + +#### 2. Separators + +There are only two kind of separators: + +- `.` for scoped namespaces (`api::`, `plugin::`) and components (``) +- `::` for others (`admin`, `strapi`, `global`) + +#### 3. Names + +UID names can be any alphanumeric string. + +:::caution +A UID is unique for the kind of resource it's attached to, but **different resource can share the same UID**. + +For instance, it's completely possible to have both a `service` and a `schema` identified by `api::article.article`. + +Since **TypeScript is a structural type system**, it means that **different UIDs resolving to the same literal type can match each other**, thus making it possible to send a service UID to a method expecting a schema UID (if they share the same format). +::: + +### Compatibility Table + +The following table shows, for each kind of UID, what resource they can be associated with. + +:::note +ContentType and Component are referring to both the related schema and entity. +::: + +| | ContentType | Component | Middleware | Policy | Controller | Service | +|--------------------------|:------------------:|:------------------:|:------------------:|:------------------:|:------------------:|:------------------:| +| `api::.` | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| `plugin::.` | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| `.` | :x: | :white_check_mark: | :x: | :x: | :x: | :x: | +| `strapi::` | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark: | :x: | :x: | +| `admin::` | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| `global::` | :x: | :x: | :white_check_mark: | :white_check_mark: | :x: | :x: | + +### Usage + +When referencing resource by their UID you'll need to use the `UID` namespace exported from `@strapi/types`. + +```typescript +import type { UID } from '@strapi/types'; +``` + +This namespace contains shortcuts to dynamic UID types built from the public registries so that they always adapt to the current context. + +:::danger +The `UID` namespace is designed to be the main interface used by developers. + +Do not use the `Internal.UID` namespace except if you know what you're doing (low level extends clause, isolated internal code, etc...). +::: + +#### Basic Example + +A common usage is to declare a function that takes a UID as a parameter. + +For our example, let's imagine we want to fetch an entity based on the provided resource UID. + +```typescript +import type { UID, Data } from '@strapi/types'; + +declare function fetch(uid: UID.ContentType): Data.ContentType; +``` + +:::tip +To find an exhaustive list of available UID types, take a look at the [related API reference](http://foo) +::: + +#### Parameter Type Inference + +Now let's say we want to adapt the return type of our function, so that it matches the given UID. + +```typescript +fetch('api::article.article'); +// ^ this should return a Data.Entity<'api::article.article'> + +fetch('admin::user'); +// ^ this should return a Data.Entity<'admin::user'> +``` +To do that, we'll need the function to be able to provide us with the current `uid` type based on usage. + +```typescript +import type { UID, Data } from '@strapi/types'; + +declare function fetch(uid: T): Data.ContentType; +``` + +So what's changed here? + +1. We've forced the `uid` type to be inferred upon usage and stored in a type variable called `T`. +2. We've then re-used `T` to parametrize the `Data.ContentType` type. + +`fetch` will now always return the correct entity depending on which `UID` is sent. + +:::caution +When writing actual code, avoid using `T` as a type variable, and always use meaningful names that will help other developers understand what the variable represents. + +For instance, in our example we could use `TContentTypeUID` instead of just `T`. +::: + +#### Going Further + +It's completely possible to reference `T` in other generic parameters. + +Let's add the possibility to select which fields we want to return for our entity. + +```typescript +import type { UID, Data, Schema } from '@strapi/types'; + +declare function fetch< + T extends UID.ContentType, + F extends Schema.AttributeNames +>(uid: T, fields: F[]): Data.ContentType; +``` + +:::tip +You may have noticed that we're using the inferred UID type (`T`) to reference both: +- An entity (`Data.Entity`) +- A schema (`Schema.AttributeNames`) + +This is because they share the same format and can be used interchangeably. + +For more information, take a look at the [format](#format) and [compatibility table](#compatibility-table) sections. +::: diff --git a/docs/docs/guides/05-type-system/02-concepts/03-public-registry.mdx b/docs/docs/guides/05-type-system/02-concepts/03-public-registry.mdx new file mode 100644 index 0000000000..3ee780a471 --- /dev/null +++ b/docs/docs/guides/05-type-system/02-concepts/03-public-registry.mdx @@ -0,0 +1,316 @@ +--- +title: Public Registries +tags: + - typescript + - type system + - type + - concepts + - public +toc_max_heading_level: 5 +--- + +import Tabs from '@theme/Tabs' +import TabItem from '@theme/TabItem' + +### Context + +#### Why? + +The Strapi Type System is designed to provide developers with a fully customizable experience. + +It's engineered to adapt and modify its type definitions automatically according to the context of each application and associated resources. + +This adaptability extends to various components such as schemas, services, controllers, and more. + +:::note +See the [type system principles](../philosophy#key-principles) page for more information about the mission. +::: + +#### How? + +To meet this requirement, the Type System employs "public registries". + +In simple terms, public registries are basic indexed interface definitions that are made publicly available by the types package. + +```typescript +export interface MyRegistry { + [key: string]: GenericResourceDefinition; +} +``` + +Every resource comes with its own registry that has a set of rules acting as the default state (the index). + +Because every registry can be augmented (publicly exported), developers have the freedom to inject their own definitions. + +Doing so allows Strapi APIs to respond in a way that aligns with the developers' custom definitions. + +:::info[Did you know?] +The `Schema` and `UID` namespaces rely solely on public registries to infer their type definitions. + +This is why it's heavily encouraged to use them over their low level counter-parts. +::: + +### How it works + +#### Architecture + +```mermaid +flowchart TB; + %% Main node + ContentTypeRegistry("Content-Types Registry") + + %% App + subgraph Application + %% Nodes + UserApp["User Application"] + UserSchema(["Application Types"]) + + %% Links + UserApp -- "generates" ---> UserSchema + end + + %% Type System + subgraph TypesPackage["@strapi/types"] + %% Nodes + TypeSystem["Type System"] + Types{{Types}} + Registries{{Registries}} + UID{{UID}} + Schema{{Schema}} + + %% Links + TypeSystem -- "exports" ---> Types & Registries + Types -- "exports" ---> UID & Schema -- "uses" ---> ContentTypeRegistry + Registries -- "exports" ----> ContentTypeRegistry + end + + %% Strapi + subgraph StrapiPackage["@strapi/strapi"] + %% Nodes + Strapi["Strapi"] + APIs[["APIs"]] + + %% Links + Strapi -- "exports" --> APIs + APIs -- "uses" --> Types + end + + %% This link needs to be placed last to preserve the layout + UserSchema -- "augments" -----> ContentTypeRegistry +``` + +#### Usage + +##### 1. Registry Definition + +Creating a new registry is as simple as exporting an indexed interface from the right namespace. + +Let's declare the content-type schema registry. + +It should accept: +- Content-type UIDs as keys +- Content-type schemas as values + +```ts title="@strapi/types/public/registries.ts" +import type { Internal, Struct } from '@strapi/types'; + +export interface ContentTypeSchemas { + [key: Internal.UID.ContentType]: Struct.ContentTypeSchema; +} +``` + +:::note +We use low level types to define our index (`Internal`/`Struct`) to keep it as generic as possible. +::: + +##### 2. Dynamic Type Definitions + +###### UID.ContentType + +To define `UID.ContentType`, we extract every key (`Internal.Registry.Keys`) from the public content-type registry (`Public.ContentTypeSchemas`) that matches with the base definition of a content-type UID (`Internal.UID.ContentType`). + +```ts title="@strapi/types/uid.ts" +import type { Internal, Public } from '@strapi/types' + +export type ContentType = Internal.Registry.Keys< + Public.ContentTypeSchemas, + Internal.UID.ContentType +>; +``` + +:::note +Only selecting keys that extend `Internal.UID.ContentType` ensures we don't end up with manually added malformed keys, and tells the type-checker we're confident about what's in our union type. +::: + +###### Schema.ContentType + +To declare `Schema.ContentType`, we simply query the content-type schema registry (`Public.ContentTypeSchemas`) with the provided content-type UID (`TUID`). + +:::note +Since `UID.ContentType` (`TUID`) is [dynamically built based on actual keys](#uidcontenttype), we know for sure that there will be a valid corresponding schema in the registry. +::: + +```ts title="@strapi/types/schema.ts" +import type { UID, Public } from '@strapi/types' + +export type ContentType = Public.ContentTypeSchemas[TUID]; +``` + +##### 3. API Design + +To create types for a dynamic API (_aka one that reacts to its context_), simply use dynamic type definitions such as `UID`, `Data` or `Schema`. + +```ts title="@strapi/core/document-service.ts" +import type { Data, UID } from '@strapi/types'; + +export type findOne(uid: TUID): Data.ContentType; +``` + +:::caution +Remember to use dynamic type definitions (`UID`, `Data`, `Schema`) and not static ones (`Internal`, `Struct`). +::: + +:::info[Reminder] +Registries are **indexed**, which means that: +- **When augmented** (_e.g. in users' applications_), they'll return **strongly typed values** that correspond to the defined types. +- **When empty** (_e.g. in Strapi codebase_), they'll return **generic low level types** based on their index definition. + + + + ```ts + import type { UID } from '@strapi/types'; + + const uid: UID.ContentType; + // ^ 'api::article.article' | 'admin::user' + ``` + + + ```ts + import type { UID } from '@strapi/types'; + + const uid: UID.ContentType; + // ^ `admin::${string}` | `api::${string}.${string}` | `plugin::${string}.${string}` | `strapi::${string}` + ``` + + +::: + +##### 4. Type Augmentation + +###### Manual Augmentation + +It's possible to manually augment the public registries to create tailor-made experiences. + +```ts title="my-app/index.d.ts" +import type { Struct } from '@strapi/strapi'; + +interface ApiArticleArticle extends Struct.ContentTypeSchema { + // ... +} + +declare module '@strapi/strapi' { + export module Public { + export interface ContentTypeSchemas { + 'api::article.article': ApiArticleArticle; + } + } +} +```` + +This will force every type that depends on the `Public.ContentTypeSchemas` registry to recognize `'api::article.article'` as the only valid UID and `ApiArticleArticle` the only valid schema. + +:::note +In the context of a Strapi application, developers are strongly encouraged to use types exported by `@strapi/strapi` and not `@strapi/types`. + +This is to allow having both an internal (`@strapi/types`) and a public (`@strapi/strapi`) types API. +::: + +###### Automated Augmentation + +To ease the registries augmentation, Strapi offers an automated way of generating types and extending the registries. + +The process will generate type definitions based on the user application state (`schemas`), then augment the registry using the created types. + + + + Generate the types once. + + ```shell title="my-app/" + yarn strapi ts:generate-types + ``` + + + Start the application in dev mode, and generate types on every server restart. + + Useful when working with the content-type builder. + + ```shell title="my-app/" + yarn develop + ``` + + + +The generated types will automatically augment the corresponding registries. + +```ts title="my-app/types/generated/contentTypes.d.ts" +import type { Struct } from '@strapi/strapi'; + +interface ApiArticleArticle extends Struct.ContentTypeSchema { + // ... +} + +interface AdminUser extends Struct.ContentTypeSchema { + // ... +} + +declare module '@strapi/strapi' { + export module Public { + export interface ContentTypeSchemas { + 'api::article.article': ApiArticleArticle; + 'admin::user': AdminUser; + } + } +} +``` + +--- + +When coupling everything together, the end result is a TypeScript developer experience automatically adjusted to the current context. + + + + + ```ts title="my-app/src/index.ts" + export default () => ({ + bootstrap() { + strapi.findOne('ap'); + // ^ TypeScript will autocomplete with "api::article.article" + + strapi.findOne('ad'); + // ^ TypeScript will autocomplete with "admin::user" + + strapi.findOne('api::blog.blog'); + // ^ Error, TypeScript will complain + } + }) + ``` + + + ```ts title="@strapi/strapi/document-service.ts" + import type { UID } from '@strapi/types'; + + export const findOne(uid: TUID) { + // ... + } + + findOne('admin::foo'); + // ^ Valid, matches 'admin::${string}' + + findOne('plugin::bar.bar'); + // ^ Valid, matches 'plugin::${string}.${string}' + + findOne('baz'); + // ^ Error, does not correspond to any content-type UID format + ``` + + diff --git a/docs/docs/guides/05-type-system/02-concepts/index.md b/docs/docs/guides/05-type-system/02-concepts/index.md new file mode 100644 index 0000000000..72dde87a48 --- /dev/null +++ b/docs/docs/guides/05-type-system/02-concepts/index.md @@ -0,0 +1,15 @@ +--- +title: Concepts +tags: + - typescript + - type system + - type + - concepts +--- + +```mdx-code-block +import DocCardList from '@theme/DocCardList'; +import { useCurrentSidebarCategory } from '@docusaurus/theme-common'; + + +``` diff --git a/docs/docusaurus.config.js b/docs/docusaurus.config.js index 62324be1c1..4316480810 100644 --- a/docs/docusaurus.config.js +++ b/docs/docusaurus.config.js @@ -17,6 +17,7 @@ const config = { organizationName: 'strapi', projectName: 'strapi', trailingSlash: false, + themes: ['@docusaurus/theme-mermaid'], // Even if you don't use internalization, you can use this field to set useful // metadata like html lang. For example, if your site is Chinese, you may want @@ -25,6 +26,9 @@ const config = { defaultLocale: 'en', locales: ['en'], }, + markdown: { + mermaid: true, + }, plugins: [ () => ({ name: 'resolve-react', diff --git a/docs/package.json b/docs/package.json index d193eb1402..1fccf5746b 100644 --- a/docs/package.json +++ b/docs/package.json @@ -29,6 +29,7 @@ "@cmfcmf/docusaurus-search-local": "1.1.0", "@docusaurus/core": "3.1.1", "@docusaurus/preset-classic": "3.1.1", + "@docusaurus/theme-mermaid": "3.1.1", "@mdx-js/react": "^3.0.0", "clsx": "^1.1.1", "prism-react-renderer": "^2.1.0", diff --git a/docs/yarn.lock b/docs/yarn.lock index 87ad3776ef..e7cb43e697 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -2099,6 +2099,13 @@ __metadata: languageName: node linkType: hard +"@braintree/sanitize-url@npm:^6.0.1": + version: 6.0.4 + resolution: "@braintree/sanitize-url@npm:6.0.4" + checksum: 52de7e19df29039134e2f0fbe6d11dbc15423d18799dc5306fbc2c92d6a7bd0e6c3c079c09be99260647cc85c3ca910e2099d819965a1d8604d05e5d3f3bb358 + languageName: node + linkType: hard + "@cmfcmf/docusaurus-search-local@npm:1.1.0": version: 1.1.0 resolution: "@cmfcmf/docusaurus-search-local@npm:1.1.0" @@ -2582,6 +2589,24 @@ __metadata: languageName: node linkType: hard +"@docusaurus/theme-mermaid@npm:3.1.1": + version: 3.1.1 + resolution: "@docusaurus/theme-mermaid@npm:3.1.1" + dependencies: + "@docusaurus/core": "npm:3.1.1" + "@docusaurus/module-type-aliases": "npm:3.1.1" + "@docusaurus/theme-common": "npm:3.1.1" + "@docusaurus/types": "npm:3.1.1" + "@docusaurus/utils-validation": "npm:3.1.1" + mermaid: "npm:^10.4.0" + tslib: "npm:^2.6.0" + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + checksum: 8af63823d953bc0335c5aae194084aaba8ce458becebad32607635769526e4aecd558bc22ba1b7bd2bb224ae160492b6ec122d037f2772edb786445c7feb2572 + languageName: node + linkType: hard + "@docusaurus/theme-search-algolia@npm:3.1.1": version: 3.1.1 resolution: "@docusaurus/theme-search-algolia@npm:3.1.1" @@ -3269,6 +3294,29 @@ __metadata: languageName: node linkType: hard +"@types/d3-scale-chromatic@npm:^3.0.0": + version: 3.0.3 + resolution: "@types/d3-scale-chromatic@npm:3.0.3" + checksum: cc5488af1136c3f9e28aa3c3ee2dc3e5e843c666f64360fb3870f0b8679cd2ee844edaa5a93504a9665deb98cb3c2ae2257d610c338fa8caa4a31ab6fdeb2f15 + languageName: node + linkType: hard + +"@types/d3-scale@npm:^4.0.3": + version: 4.0.8 + resolution: "@types/d3-scale@npm:4.0.8" + dependencies: + "@types/d3-time": "npm:*" + checksum: 376e4f2199ee6db70906651587a4521976920fa5eaa847a976c434e7a8171cbfeeab515cc510c5130b1f64fcf95b9750a7fd21dfc0a40fc3398641aa7dd4e7e2 + languageName: node + linkType: hard + +"@types/d3-time@npm:*": + version: 3.0.3 + resolution: "@types/d3-time@npm:3.0.3" + checksum: 4e6bf24ec422f0893747e5020592e107bb3d96764a43d5f0bff666202bd71f052c73f735b50ec66296a6efd5766ca40b6a4e8ce3bbc61217dbe9467340608c12 + languageName: node + linkType: hard + "@types/debug@npm:^4.0.0": version: 4.1.12 resolution: "@types/debug@npm:4.1.12" @@ -3422,6 +3470,15 @@ __metadata: languageName: node linkType: hard +"@types/mdast@npm:^3.0.0": + version: 3.0.15 + resolution: "@types/mdast@npm:3.0.15" + dependencies: + "@types/unist": "npm:^2" + checksum: 050a5c1383928b2688dd145382a22535e2af87dc3fd592c843abb7851bcc99893a1ee0f63be19fc4e89779387ec26a57486cfb425b016c0b2a98a17fc4a1e8b3 + languageName: node + linkType: hard + "@types/mdast@npm:^4.0.0, @types/mdast@npm:^4.0.2": version: 4.0.3 resolution: "@types/mdast@npm:4.0.3" @@ -3632,6 +3689,13 @@ __metadata: languageName: node linkType: hard +"@types/unist@npm:^2": + version: 2.0.10 + resolution: "@types/unist@npm:2.0.10" + checksum: e2924e18dedf45f68a5c6ccd6015cd62f1643b1b43baac1854efa21ae9e70505db94290434a23da1137d9e31eb58e54ca175982005698ac37300a1c889f6c4aa + languageName: node + linkType: hard + "@types/unist@npm:^3.0.0": version: 3.0.2 resolution: "@types/unist@npm:3.0.2" @@ -4835,6 +4899,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:7, commander@npm:^7.2.0": + version: 7.2.0 + resolution: "commander@npm:7.2.0" + checksum: 9973af10727ad4b44f26703bf3e9fdc323528660a7590efe3aa9ad5042b4584c0deed84ba443f61c9d6f02dade54a5a5d3c95e306a1e1630f8374ae6db16c06d + languageName: node + linkType: hard + "commander@npm:^10.0.0": version: 10.0.1 resolution: "commander@npm:10.0.1" @@ -4856,13 +4927,6 @@ __metadata: languageName: node linkType: hard -"commander@npm:^7.2.0": - version: 7.2.0 - resolution: "commander@npm:7.2.0" - checksum: 9973af10727ad4b44f26703bf3e9fdc323528660a7590efe3aa9ad5042b4584c0deed84ba443f61c9d6f02dade54a5a5d3c95e306a1e1630f8374ae6db16c06d - languageName: node - linkType: hard - "commander@npm:^8.3.0": version: 8.3.0 resolution: "commander@npm:8.3.0" @@ -5049,6 +5113,15 @@ __metadata: languageName: node linkType: hard +"cose-base@npm:^1.0.0": + version: 1.0.3 + resolution: "cose-base@npm:1.0.3" + dependencies: + layout-base: "npm:^1.0.0" + checksum: 52e1f4ae173738aebe14395e3f865dc10ce430156554bab52f4b8ef0c583375644348c2a226b83d97eebc7d35340919e7bc10d23a3e2fe51b853bf56f27b5da7 + languageName: node + linkType: hard + "cosmiconfig@npm:^6.0.0": version: 6.0.0 resolution: "cosmiconfig@npm:6.0.0" @@ -5380,6 +5453,397 @@ __metadata: languageName: node linkType: hard +"cytoscape-cose-bilkent@npm:^4.1.0": + version: 4.1.0 + resolution: "cytoscape-cose-bilkent@npm:4.1.0" + dependencies: + cose-base: "npm:^1.0.0" + peerDependencies: + cytoscape: ^3.2.0 + checksum: 9ec2999159af62f1a251bf1e146a9a779085c4fdb1b8146596208f0097c0512fc4bffda53d3b00c87a1e8ae5024db3ebfb97162115216f5b4d024e314f4a03bb + languageName: node + linkType: hard + +"cytoscape@npm:^3.28.1": + version: 3.28.1 + resolution: "cytoscape@npm:3.28.1" + dependencies: + heap: "npm:^0.2.6" + lodash: "npm:^4.17.21" + checksum: 3f7adf3675e26bf4e14dadf3932f68b7fe9a4aef2f5598251d57369dc86d94db587036dbef26954c5e92d8ec6a1c2a0af888dc18d9acd9b0a8a01c7eddf11775 + languageName: node + linkType: hard + +"d3-array@npm:1 - 2": + version: 2.12.1 + resolution: "d3-array@npm:2.12.1" + dependencies: + internmap: "npm:^1.0.0" + checksum: 9fdfb91f428915006e126090fe9aa9d5fcbecc78e925eceb32de9dfb989135f6ad940a8f1b086d0b569523679f85453c5335772aa9e6d5d41b480c2610857c7f + languageName: node + linkType: hard + +"d3-array@npm:2 - 3, d3-array@npm:2.10.0 - 3, d3-array@npm:2.5.0 - 3, d3-array@npm:3, d3-array@npm:^3.2.0": + version: 3.2.4 + resolution: "d3-array@npm:3.2.4" + dependencies: + internmap: "npm:1 - 2" + checksum: 5800c467f89634776a5977f6dae3f4e127d91be80f1d07e3e6e35303f9de93e6636d014b234838eea620f7469688d191b3f41207a30040aab750a63c97ec1d7c + languageName: node + linkType: hard + +"d3-axis@npm:3": + version: 3.0.0 + resolution: "d3-axis@npm:3.0.0" + checksum: 15ec43ecbd4e7b606fcda60f67a522e45576dfd6aa83dff47f3e91ef6c8448841a09cd91f630b492250dcec67c6ea64463510ead5e632ff6b827aeefae1d42ad + languageName: node + linkType: hard + +"d3-brush@npm:3": + version: 3.0.0 + resolution: "d3-brush@npm:3.0.0" + dependencies: + d3-dispatch: "npm:1 - 3" + d3-drag: "npm:2 - 3" + d3-interpolate: "npm:1 - 3" + d3-selection: "npm:3" + d3-transition: "npm:3" + checksum: fa3a461b62f0f0ee6fe41f5babf45535a0a8f6d4999f675fb1dce932ee02eff72dec14c7296af31ca15998dc0141ccf5d02aa6499363f8bf2941d90688a1d644 + languageName: node + linkType: hard + +"d3-chord@npm:3": + version: 3.0.1 + resolution: "d3-chord@npm:3.0.1" + dependencies: + d3-path: "npm:1 - 3" + checksum: 4febcdca4fdc8ba91fc4f7545f4b6321c440150dff80c1ebef887db07bb4200395dfebede63b257393259de07f914da10842da5ab3135e1e281e33ad153e0849 + languageName: node + linkType: hard + +"d3-color@npm:1 - 3, d3-color@npm:3": + version: 3.1.0 + resolution: "d3-color@npm:3.1.0" + checksum: 536ba05bfd9f4fcd6fa289b5974f5c846b21d186875684637e22bf6855e6aba93e24a2eb3712985c6af3f502fbbfa03708edb72f58142f626241a8a17258e545 + languageName: node + linkType: hard + +"d3-contour@npm:4": + version: 4.0.2 + resolution: "d3-contour@npm:4.0.2" + dependencies: + d3-array: "npm:^3.2.0" + checksum: 0b252267e0c3c5e97d7e0c720bd35654de99f981199f7240d7dd1acfd4e2d5bf1638829f6db486452eff9c38608efa4a6ab5a0d1525131735c011ee7be3cb4ba + languageName: node + linkType: hard + +"d3-delaunay@npm:6": + version: 6.0.4 + resolution: "d3-delaunay@npm:6.0.4" + dependencies: + delaunator: "npm:5" + checksum: 4588e2872d4154daaf2c3f34fefe74e43b909cc460238a7b02823907ca6dd109f2c488c57c8551f1a2607fe4b44fdf24e3a190cea29bca70ef5606678dd9e2de + languageName: node + linkType: hard + +"d3-dispatch@npm:1 - 3, d3-dispatch@npm:3": + version: 3.0.1 + resolution: "d3-dispatch@npm:3.0.1" + checksum: 2b82f41bf4ef88c2f9033dfe32815b67e2ef1c5754a74137a74c7d44d6f0d6ecfa934ac56ed8afe358f6c1f06462e8aa42ca0a388397b5b77a42721570e80487 + languageName: node + linkType: hard + +"d3-drag@npm:2 - 3, d3-drag@npm:3": + version: 3.0.0 + resolution: "d3-drag@npm:3.0.0" + dependencies: + d3-dispatch: "npm:1 - 3" + d3-selection: "npm:3" + checksum: 80bc689935e5a46ee92b2d7f71e1c792279382affed9fbcf46034bff3ff7d3f50cf61a874da4bdf331037292b9e7dca5c6401a605d4bb699fdcb4e0c87e176ec + languageName: node + linkType: hard + +"d3-dsv@npm:1 - 3, d3-dsv@npm:3": + version: 3.0.1 + resolution: "d3-dsv@npm:3.0.1" + dependencies: + commander: "npm:7" + iconv-lite: "npm:0.6" + rw: "npm:1" + bin: + csv2json: bin/dsv2json.js + csv2tsv: bin/dsv2dsv.js + dsv2dsv: bin/dsv2dsv.js + dsv2json: bin/dsv2json.js + json2csv: bin/json2dsv.js + json2dsv: bin/json2dsv.js + json2tsv: bin/json2dsv.js + tsv2csv: bin/dsv2dsv.js + tsv2json: bin/dsv2json.js + checksum: a628ac42a272466940f713f310db2e5246690b22035121dc1230077070c9135fb7c9b4d260f093fcadf63b0528202a1953107448a4be3a860c4f42f50d09504d + languageName: node + linkType: hard + +"d3-ease@npm:1 - 3, d3-ease@npm:3": + version: 3.0.1 + resolution: "d3-ease@npm:3.0.1" + checksum: 985d46e868494e9e6806fedd20bad712a50dcf98f357bf604a843a9f6bc17714a657c83dd762f183173dcde983a3570fa679b2bc40017d40b24163cdc4167796 + languageName: node + linkType: hard + +"d3-fetch@npm:3": + version: 3.0.1 + resolution: "d3-fetch@npm:3.0.1" + dependencies: + d3-dsv: "npm:1 - 3" + checksum: cd35d55f8fbb1ea1e37be362a575bb0161449957133aa5b45b9891889b2aca1dc0769c240a236736e33cd823e820a0e73fb3744582307a5d26d1df7bed0ccecb + languageName: node + linkType: hard + +"d3-force@npm:3": + version: 3.0.0 + resolution: "d3-force@npm:3.0.0" + dependencies: + d3-dispatch: "npm:1 - 3" + d3-quadtree: "npm:1 - 3" + d3-timer: "npm:1 - 3" + checksum: 85945f8d444d78567009518f0ab54c0f0c8873eb8eb9a2ff0ab667b0f81b419e101a411415d4a2c752547ec7143f89675e8c33b8f111e55e5579a04cb7f4591c + languageName: node + linkType: hard + +"d3-format@npm:1 - 3, d3-format@npm:3": + version: 3.1.0 + resolution: "d3-format@npm:3.1.0" + checksum: a0fe23d2575f738027a3db0ce57160e5a473ccf24808c1ed46d45ef4f3211076b34a18b585547d34e365e78dcc26dd4ab15c069731fc4b1c07a26bfced09ea31 + languageName: node + linkType: hard + +"d3-geo@npm:3": + version: 3.1.1 + resolution: "d3-geo@npm:3.1.1" + dependencies: + d3-array: "npm:2.5.0 - 3" + checksum: dc5e980330d891dabf92869b98871b05ca2021c64d7ef253bcfd4f2348839ad33576fba474baecc2def86ebd3d943a11d93c0af26be0a2694f5bd59824838133 + languageName: node + linkType: hard + +"d3-hierarchy@npm:3": + version: 3.1.2 + resolution: "d3-hierarchy@npm:3.1.2" + checksum: 497b79dc6c35e28b21e8a7b94db92876abd1d4ec082d9803a07ea8964e55b0e71c511a21489363a36f1456f069adb8ff7d33c633678730d6ae961ed350b27733 + languageName: node + linkType: hard + +"d3-interpolate@npm:1 - 3, d3-interpolate@npm:1.2.0 - 3, d3-interpolate@npm:3": + version: 3.0.1 + resolution: "d3-interpolate@npm:3.0.1" + dependencies: + d3-color: "npm:1 - 3" + checksum: 988d66497ef5c190cf64f8c80cd66e1e9a58c4d1f8932d776a8e3ae59330291795d5a342f5a97602782ccbef21a5df73bc7faf1f0dc46a5145ba6243a82a0f0e + languageName: node + linkType: hard + +"d3-path@npm:1": + version: 1.0.9 + resolution: "d3-path@npm:1.0.9" + checksum: 6ce1747837ea2a449d9ea32e169a382978ab09a4805eb408feb6bbc12cb5f5f6ce29aefc252dd9a815d420f4813d672f75578b78b3bbaf7811f54d8c7f93fd11 + languageName: node + linkType: hard + +"d3-path@npm:1 - 3, d3-path@npm:3, d3-path@npm:^3.1.0": + version: 3.1.0 + resolution: "d3-path@npm:3.1.0" + checksum: 8e97a9ab4930a05b18adda64cf4929219bac913a5506cf8585631020253b39309549632a5cbeac778c0077994442ddaaee8316ee3f380e7baf7566321b84e76a + languageName: node + linkType: hard + +"d3-polygon@npm:3": + version: 3.0.1 + resolution: "d3-polygon@npm:3.0.1" + checksum: c4fa2ed19dcba13fd341815361d27e64597aa0d38d377e401e1353c4acbe8bd73c0afb3e49a1cf4119fadc3651ec8073d06aa6d0e34e664c868d071e58912cd1 + languageName: node + linkType: hard + +"d3-quadtree@npm:1 - 3, d3-quadtree@npm:3": + version: 3.0.1 + resolution: "d3-quadtree@npm:3.0.1" + checksum: 1915b6a7b031fc312f9af61947072db9468c5a2b03837f6a90b38fdaebcd0ea17a883bffd94d16b8a6848e81711a06222f7d39f129386ef1850297219b8d32ba + languageName: node + linkType: hard + +"d3-random@npm:3": + version: 3.0.1 + resolution: "d3-random@npm:3.0.1" + checksum: 9f41d6ca3a1826cea8d88392917b5039504337d442a4d1357c870fa3031701e60209a2689a6ddae7df8fca824383d038c957eb545bc49a7428c71aaf3b11f56f + languageName: node + linkType: hard + +"d3-sankey@npm:^0.12.3": + version: 0.12.3 + resolution: "d3-sankey@npm:0.12.3" + dependencies: + d3-array: "npm:1 - 2" + d3-shape: "npm:^1.2.0" + checksum: d5c679135a26d435e9970de3fc0778c6ef5c911f0c878b246939517b57a8daa2e2db6ef99318a0dad16e6079e4b89ef9166f1f661d8d247637875b764628094d + languageName: node + linkType: hard + +"d3-scale-chromatic@npm:3": + version: 3.1.0 + resolution: "d3-scale-chromatic@npm:3.1.0" + dependencies: + d3-color: "npm:1 - 3" + d3-interpolate: "npm:1 - 3" + checksum: 25df6a7c621b9171df8b2225e98e41c0a6bcac4de02deb4807280b31116e8f495c5ac93301796098ee5b698cb690154e8138d90d72fd1fe36744c60e02a3d8c4 + languageName: node + linkType: hard + +"d3-scale@npm:4": + version: 4.0.2 + resolution: "d3-scale@npm:4.0.2" + dependencies: + d3-array: "npm:2.10.0 - 3" + d3-format: "npm:1 - 3" + d3-interpolate: "npm:1.2.0 - 3" + d3-time: "npm:2.1.1 - 3" + d3-time-format: "npm:2 - 4" + checksum: e2dc4243586eae2a0fdf91de1df1a90d51dfacb295933f0ca7e9184c31203b01436bef69906ad40f1100173a5e6197ae753cb7b8a1a8fcfda43194ea9cad6493 + languageName: node + linkType: hard + +"d3-selection@npm:2 - 3, d3-selection@npm:3": + version: 3.0.0 + resolution: "d3-selection@npm:3.0.0" + checksum: 0e5acfd305b31628b7be5009ba7303d84bb34817a88ed4dde9c8bd9c23528573fc5272f89fc04e5be03d2cbf5441a248d7274aaf55a8ef3dad46e16333d72298 + languageName: node + linkType: hard + +"d3-shape@npm:3": + version: 3.2.0 + resolution: "d3-shape@npm:3.2.0" + dependencies: + d3-path: "npm:^3.1.0" + checksum: 2e861f4d4781ee8abd85d2b435f848d667479dcf01a4e0db3a06600a5bdeddedb240f88229ec7b3bf7fa300c2b3526faeaf7e75f9a24dbf4396d3cc5358ff39d + languageName: node + linkType: hard + +"d3-shape@npm:^1.2.0": + version: 1.3.7 + resolution: "d3-shape@npm:1.3.7" + dependencies: + d3-path: "npm:1" + checksum: 1e40fdcfdc8edc9c53a77a6aaea2dbf31bf06df12ebd66cc8d91f76bbde753049ad21dfee0577f7dc5d0a4468554ede4783f6df7d809e291745334dba977c09e + languageName: node + linkType: hard + +"d3-time-format@npm:2 - 4, d3-time-format@npm:4": + version: 4.1.0 + resolution: "d3-time-format@npm:4.1.0" + dependencies: + d3-time: "npm:1 - 3" + checksum: ffc0959258fbb90e3890bfb31b43b764f51502b575e87d0af2c85b85ac379120d246914d07fca9f533d1bcedc27b2841d308a00fd64848c3e2cad9eff5c9a0aa + languageName: node + linkType: hard + +"d3-time@npm:1 - 3, d3-time@npm:2.1.1 - 3, d3-time@npm:3": + version: 3.1.0 + resolution: "d3-time@npm:3.1.0" + dependencies: + d3-array: "npm:2 - 3" + checksum: c110bed295ce63e8180e45b82a9b0ba114d5f33ff315871878f209c1a6d821caa505739a2b07f38d1396637155b8e7372632dacc018e11fbe8ceef58f6af806d + languageName: node + linkType: hard + +"d3-timer@npm:1 - 3, d3-timer@npm:3": + version: 3.0.1 + resolution: "d3-timer@npm:3.0.1" + checksum: 004128602bb187948d72c7dc153f0f063f38ac7a584171de0b45e3a841ad2e17f1e40ad396a4af9cce5551b6ab4a838d5246d23492553843d9da4a4050a911e2 + languageName: node + linkType: hard + +"d3-transition@npm:2 - 3, d3-transition@npm:3": + version: 3.0.1 + resolution: "d3-transition@npm:3.0.1" + dependencies: + d3-color: "npm:1 - 3" + d3-dispatch: "npm:1 - 3" + d3-ease: "npm:1 - 3" + d3-interpolate: "npm:1 - 3" + d3-timer: "npm:1 - 3" + peerDependencies: + d3-selection: 2 - 3 + checksum: 02571636acb82f5532117928a87fe25de68f088c38ab4a8b16e495f0f2d08a3fd2937eaebdefdfcf7f1461545524927d2632d795839b88d2e4c71e387aaaffac + languageName: node + linkType: hard + +"d3-zoom@npm:3": + version: 3.0.0 + resolution: "d3-zoom@npm:3.0.0" + dependencies: + d3-dispatch: "npm:1 - 3" + d3-drag: "npm:2 - 3" + d3-interpolate: "npm:1 - 3" + d3-selection: "npm:2 - 3" + d3-transition: "npm:2 - 3" + checksum: 0e6e5c14e33c4ecdff311a900dd037dea407734f2dd2818988ed6eae342c1799e8605824523678bd404f81e37824cc588f62dbde46912444c89acc7888036c6b + languageName: node + linkType: hard + +"d3@npm:^7.4.0, d3@npm:^7.8.2": + version: 7.9.0 + resolution: "d3@npm:7.9.0" + dependencies: + d3-array: "npm:3" + d3-axis: "npm:3" + d3-brush: "npm:3" + d3-chord: "npm:3" + d3-color: "npm:3" + d3-contour: "npm:4" + d3-delaunay: "npm:6" + d3-dispatch: "npm:3" + d3-drag: "npm:3" + d3-dsv: "npm:3" + d3-ease: "npm:3" + d3-fetch: "npm:3" + d3-force: "npm:3" + d3-format: "npm:3" + d3-geo: "npm:3" + d3-hierarchy: "npm:3" + d3-interpolate: "npm:3" + d3-path: "npm:3" + d3-polygon: "npm:3" + d3-quadtree: "npm:3" + d3-random: "npm:3" + d3-scale: "npm:4" + d3-scale-chromatic: "npm:3" + d3-selection: "npm:3" + d3-shape: "npm:3" + d3-time: "npm:3" + d3-time-format: "npm:4" + d3-timer: "npm:3" + d3-transition: "npm:3" + d3-zoom: "npm:3" + checksum: b0b418996bdf279b01f5c7a0117927f9ad3e833c9ce4657550ce6f6ace70b70cf829c4144b01df0be5a0f716d4e5f15ab0cadc5ff1ce1561d7be29ac86493d83 + languageName: node + linkType: hard + +"dagre-d3-es@npm:7.0.10": + version: 7.0.10 + resolution: "dagre-d3-es@npm:7.0.10" + dependencies: + d3: "npm:^7.8.2" + lodash-es: "npm:^4.17.21" + checksum: 09f56dd337cc7d0620d50f20913308d5e8aaffafb0b188a69b0d8ff87915599586224694be3f8d93bd8c383858d358c0140493a11a0df2508de959a4658952c2 + languageName: node + linkType: hard + +"dayjs@npm:^1.11.7": + version: 1.11.10 + resolution: "dayjs@npm:1.11.10" + checksum: 27e8f5bc01c0a76f36c656e62ab7f08c2e7b040b09e613cd4844abf03fb258e0350f0a83b02c887b84d771c1f11e092deda0beef8c6df2a1afbc3f6c1fade279 + languageName: node + linkType: hard + "debounce@npm:^1.2.1": version: 1.2.1 resolution: "debounce@npm:1.2.1" @@ -5489,6 +5953,15 @@ __metadata: languageName: node linkType: hard +"delaunator@npm:5": + version: 5.0.1 + resolution: "delaunator@npm:5.0.1" + dependencies: + robust-predicates: "npm:^3.0.2" + checksum: c378a55138d81d471a7214635b1a2c5e74f8ee06582f558df72f0c7c82c25868599ce9a18fb25a245c6c03cab886d17fb574681c78371b539dd069818703f53a + languageName: node + linkType: hard + "delegates@npm:^1.0.0": version: 1.0.0 resolution: "delegates@npm:1.0.0" @@ -5566,6 +6039,13 @@ __metadata: languageName: node linkType: hard +"diff@npm:^5.0.0": + version: 5.2.0 + resolution: "diff@npm:5.2.0" + checksum: 01b7b440f83a997350a988e9d2f558366c0f90f15be19f4aa7f1bb3109a4e153dfc3b9fbf78e14ea725717017407eeaa2271e3896374a0181e8f52445740846d + languageName: node + linkType: hard + "dir-glob@npm:^3.0.1": version: 3.0.1 resolution: "dir-glob@npm:3.0.1" @@ -5599,6 +6079,7 @@ __metadata: "@docusaurus/core": "npm:3.1.1" "@docusaurus/module-type-aliases": "npm:3.1.1" "@docusaurus/preset-classic": "npm:3.1.1" + "@docusaurus/theme-mermaid": "npm:3.1.1" "@mdx-js/react": "npm:^3.0.0" clsx: "npm:^1.1.1" docusaurus-plugin-typedoc: "npm:0.22.0" @@ -5677,6 +6158,13 @@ __metadata: languageName: node linkType: hard +"dompurify@npm:^3.0.5": + version: 3.1.0 + resolution: "dompurify@npm:3.1.0" + checksum: a8788d3510b0a5e26ae8f1beb3f079be63f417be0f7259918c273bd53f9b9eab50a0708e065caff9904ae97895cc4a7d4c66a1076021a9be0685389ad8ae4d2d + languageName: node + linkType: hard + "domutils@npm:^2.5.2, domutils@npm:^2.8.0": version: 2.8.0 resolution: "domutils@npm:2.8.0" @@ -5753,6 +6241,13 @@ __metadata: languageName: node linkType: hard +"elkjs@npm:^0.9.0": + version: 0.9.2 + resolution: "elkjs@npm:0.9.2" + checksum: 7b4c8f73e7dd61588ae772d6cc8fa68bc631f59ec9fbc81862d0bf1331c5242f9374bb2668f17c94db00d38d3114d418b14042b77c4755016e4598c2bd79bfac + languageName: node + linkType: hard + "emoji-regex@npm:^8.0.0": version: 8.0.0 resolution: "emoji-regex@npm:8.0.0" @@ -6887,6 +7382,13 @@ __metadata: languageName: node linkType: hard +"heap@npm:^0.2.6": + version: 0.2.7 + resolution: "heap@npm:0.2.7" + checksum: 6374f6510af79bf47f2cfcee265bf608e6ed2b2694875974d1cb5654ddc98af05347dcf3a42ee9a7de318b576022d6f4d00fe06fa65a4a65c4c60638375eabfe + languageName: node + linkType: hard + "history@npm:^4.9.0": version: 4.10.1 resolution: "history@npm:4.10.1" @@ -7167,7 +7669,7 @@ __metadata: languageName: node linkType: hard -"iconv-lite@npm:^0.6.2": +"iconv-lite@npm:0.6, iconv-lite@npm:^0.6.2": version: 0.6.3 resolution: "iconv-lite@npm:0.6.3" dependencies: @@ -7307,6 +7809,20 @@ __metadata: languageName: node linkType: hard +"internmap@npm:1 - 2": + version: 2.0.3 + resolution: "internmap@npm:2.0.3" + checksum: 873e0e7fcfe32f999aa0997a0b648b1244508e56e3ea6b8259b5245b50b5eeb3853fba221f96692bd6d1def501da76c32d64a5cb22a0b26cdd9b445664f805e0 + languageName: node + linkType: hard + +"internmap@npm:^1.0.0": + version: 1.0.1 + resolution: "internmap@npm:1.0.1" + checksum: 429cb9e28f393f10c73a826d71ba9e359711b7e42345bd684aba708f43b8139ce90f09b15abbf977a981474ac61615294854e5b9520d3f65187d0f6a2ff27665 + languageName: node + linkType: hard + "interpret@npm:^1.0.0": version: 1.4.0 resolution: "interpret@npm:1.4.0" @@ -7793,6 +8309,17 @@ __metadata: languageName: node linkType: hard +"katex@npm:^0.16.9": + version: 0.16.10 + resolution: "katex@npm:0.16.10" + dependencies: + commander: "npm:^8.3.0" + bin: + katex: cli.js + checksum: 367034012311c695791de4553b3e4c7a9f36d126a0cae17b97f4e8832ced2559961f9fa6d39e0116e1374013e12ac8af159eb014678f06b4acf5e547292ea3e5 + languageName: node + linkType: hard + "keyv@npm:^4.5.3": version: 4.5.4 resolution: "keyv@npm:4.5.4" @@ -7802,6 +8329,13 @@ __metadata: languageName: node linkType: hard +"khroma@npm:^2.0.0": + version: 2.1.0 + resolution: "khroma@npm:2.1.0" + checksum: a195e317bf6f3a1cba98df2677bf9bf6d14195ee0b1c3e5bc20a542cd99652682f290c196a8963956d87aed4ad65ac0bc8a15d75cddf00801fdafd148e01a5d2 + languageName: node + linkType: hard + "kind-of@npm:^6.0.0, kind-of@npm:^6.0.2": version: 6.0.3 resolution: "kind-of@npm:6.0.3" @@ -7816,6 +8350,13 @@ __metadata: languageName: node linkType: hard +"kleur@npm:^4.0.3": + version: 4.1.5 + resolution: "kleur@npm:4.1.5" + checksum: 44d84cc4eedd4311099402ef6d4acd9b2d16e08e499d6ef3bb92389bd4692d7ef09e35248c26e27f98acac532122acb12a1bfee645994ae3af4f0a37996da7df + languageName: node + linkType: hard + "latest-version@npm:^7.0.0": version: 7.0.0 resolution: "latest-version@npm:7.0.0" @@ -7835,6 +8376,13 @@ __metadata: languageName: node linkType: hard +"layout-base@npm:^1.0.0": + version: 1.0.2 + resolution: "layout-base@npm:1.0.2" + checksum: 34504e61e4770e563cf49d4a56c8c10f1da0fb452cff89a652118783189c642ebc86a300d97cbc247e59a9c1eb06a2d419982f7dd10e8eedcab2414bc46d32f8 + languageName: node + linkType: hard + "leven@npm:^3.1.0": version: 3.1.0 resolution: "leven@npm:3.1.0" @@ -7909,6 +8457,13 @@ __metadata: languageName: node linkType: hard +"lodash-es@npm:^4.17.21": + version: 4.17.21 + resolution: "lodash-es@npm:4.17.21" + checksum: 03f39878ea1e42b3199bd3f478150ab723f93cc8730ad86fec1f2804f4a07c6e30deaac73cad53a88e9c3db33348bb8ceeb274552390e7a75d7849021c02df43 + languageName: node + linkType: hard + "lodash.debounce@npm:^4.0.8": version: 4.0.8 resolution: "lodash.debounce@npm:4.0.8" @@ -8092,6 +8647,26 @@ __metadata: languageName: node linkType: hard +"mdast-util-from-markdown@npm:^1.3.0": + version: 1.3.1 + resolution: "mdast-util-from-markdown@npm:1.3.1" + dependencies: + "@types/mdast": "npm:^3.0.0" + "@types/unist": "npm:^2.0.0" + decode-named-character-reference: "npm:^1.0.0" + mdast-util-to-string: "npm:^3.1.0" + micromark: "npm:^3.0.0" + micromark-util-decode-numeric-character-reference: "npm:^1.0.0" + micromark-util-decode-string: "npm:^1.0.0" + micromark-util-normalize-identifier: "npm:^1.0.0" + micromark-util-symbol: "npm:^1.0.0" + micromark-util-types: "npm:^1.0.0" + unist-util-stringify-position: "npm:^3.0.0" + uvu: "npm:^0.5.0" + checksum: 1d334a54ddd6481ec4acf64c2c537b6463bc5113ba5a408f65c228dcc302d46837352814f11307af0f8b51dd7e4a0b887ce692e4d30ff31ff9d578b8ca82810b + languageName: node + linkType: hard + "mdast-util-from-markdown@npm:^2.0.0": version: 2.0.0 resolution: "mdast-util-from-markdown@npm:2.0.0" @@ -8308,6 +8883,15 @@ __metadata: languageName: node linkType: hard +"mdast-util-to-string@npm:^3.1.0": + version: 3.2.0 + resolution: "mdast-util-to-string@npm:3.2.0" + dependencies: + "@types/mdast": "npm:^3.0.0" + checksum: fafe201c12a0d412a875fe8540bf70b4360f3775fb7f0d19403ba7b59e50f74f730e3b405c72ad940bc8a3ec1ba311f76dfca61c4ce585dce1ccda2168ec244f + languageName: node + linkType: hard + "mdast-util-to-string@npm:^4.0.0": version: 4.0.0 resolution: "mdast-util-to-string@npm:4.0.0" @@ -8361,6 +8945,34 @@ __metadata: languageName: node linkType: hard +"mermaid@npm:^10.4.0": + version: 10.9.0 + resolution: "mermaid@npm:10.9.0" + dependencies: + "@braintree/sanitize-url": "npm:^6.0.1" + "@types/d3-scale": "npm:^4.0.3" + "@types/d3-scale-chromatic": "npm:^3.0.0" + cytoscape: "npm:^3.28.1" + cytoscape-cose-bilkent: "npm:^4.1.0" + d3: "npm:^7.4.0" + d3-sankey: "npm:^0.12.3" + dagre-d3-es: "npm:7.0.10" + dayjs: "npm:^1.11.7" + dompurify: "npm:^3.0.5" + elkjs: "npm:^0.9.0" + katex: "npm:^0.16.9" + khroma: "npm:^2.0.0" + lodash-es: "npm:^4.17.21" + mdast-util-from-markdown: "npm:^1.3.0" + non-layered-tidy-tree-layout: "npm:^2.0.2" + stylis: "npm:^4.1.3" + ts-dedent: "npm:^2.2.0" + uuid: "npm:^9.0.0" + web-worker: "npm:^1.2.0" + checksum: 458c2dec312271c1e48b6dc8ba9f8a50209e3cb633fcce2ee5d9f75a02ca0a85de28bd95d1096ac2106aecd7c33637d4084d0aca02ba9d25b3495e60d76796a2 + languageName: node + linkType: hard + "methods@npm:~1.1.2": version: 1.1.2 resolution: "methods@npm:1.1.2" @@ -8368,6 +8980,30 @@ __metadata: languageName: node linkType: hard +"micromark-core-commonmark@npm:^1.0.1": + version: 1.1.0 + resolution: "micromark-core-commonmark@npm:1.1.0" + dependencies: + decode-named-character-reference: "npm:^1.0.0" + micromark-factory-destination: "npm:^1.0.0" + micromark-factory-label: "npm:^1.0.0" + micromark-factory-space: "npm:^1.0.0" + micromark-factory-title: "npm:^1.0.0" + micromark-factory-whitespace: "npm:^1.0.0" + micromark-util-character: "npm:^1.0.0" + micromark-util-chunked: "npm:^1.0.0" + micromark-util-classify-character: "npm:^1.0.0" + micromark-util-html-tag-name: "npm:^1.0.0" + micromark-util-normalize-identifier: "npm:^1.0.0" + micromark-util-resolve-all: "npm:^1.0.0" + micromark-util-subtokenize: "npm:^1.0.0" + micromark-util-symbol: "npm:^1.0.0" + micromark-util-types: "npm:^1.0.1" + uvu: "npm:^0.5.0" + checksum: a73694d223ac8baad8ff00597a3c39d61f5b32bfd56fe4bcf295d75b2a4e8e67fb2edbfc7cc287b362b9d7f6d24fce08b6a7e8b5b155d79bcc1e4d9b2756ffb2 + languageName: node + linkType: hard + "micromark-core-commonmark@npm:^2.0.0": version: 2.0.0 resolution: "micromark-core-commonmark@npm:2.0.0" @@ -8588,6 +9224,17 @@ __metadata: languageName: node linkType: hard +"micromark-factory-destination@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-factory-destination@npm:1.1.0" + dependencies: + micromark-util-character: "npm:^1.0.0" + micromark-util-symbol: "npm:^1.0.0" + micromark-util-types: "npm:^1.0.0" + checksum: 9e2b5fb5fedbf622b687e20d51eb3d56ae90c0e7ecc19b37bd5285ec392c1e56f6e21aa7cfcb3c01eda88df88fe528f3acb91a5f57d7f4cba310bc3cd7f824fa + languageName: node + linkType: hard + "micromark-factory-destination@npm:^2.0.0": version: 2.0.0 resolution: "micromark-factory-destination@npm:2.0.0" @@ -8599,6 +9246,18 @@ __metadata: languageName: node linkType: hard +"micromark-factory-label@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-factory-label@npm:1.1.0" + dependencies: + micromark-util-character: "npm:^1.0.0" + micromark-util-symbol: "npm:^1.0.0" + micromark-util-types: "npm:^1.0.0" + uvu: "npm:^0.5.0" + checksum: fcda48f1287d9b148c562c627418a2ab759cdeae9c8e017910a0cba94bb759a96611e1fc6df33182e97d28fbf191475237298983bb89ef07d5b02464b1ad28d5 + languageName: node + linkType: hard + "micromark-factory-label@npm:^2.0.0": version: 2.0.0 resolution: "micromark-factory-label@npm:2.0.0" @@ -8647,6 +9306,18 @@ __metadata: languageName: node linkType: hard +"micromark-factory-title@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-factory-title@npm:1.1.0" + dependencies: + micromark-factory-space: "npm:^1.0.0" + micromark-util-character: "npm:^1.0.0" + micromark-util-symbol: "npm:^1.0.0" + micromark-util-types: "npm:^1.0.0" + checksum: 4432d3dbc828c81f483c5901b0c6591a85d65a9e33f7d96ba7c3ae821617a0b3237ff5faf53a9152d00aaf9afb3a9f185b205590f40ed754f1d9232e0e9157b1 + languageName: node + linkType: hard + "micromark-factory-title@npm:^2.0.0": version: 2.0.0 resolution: "micromark-factory-title@npm:2.0.0" @@ -8659,6 +9330,18 @@ __metadata: languageName: node linkType: hard +"micromark-factory-whitespace@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-factory-whitespace@npm:1.1.0" + dependencies: + micromark-factory-space: "npm:^1.0.0" + micromark-util-character: "npm:^1.0.0" + micromark-util-symbol: "npm:^1.0.0" + micromark-util-types: "npm:^1.0.0" + checksum: ef0fa682c7d593d85a514ee329809dee27d10bc2a2b65217d8ef81173e33b8e83c549049764b1ad851adfe0a204dec5450d9d20a4ca8598f6c94533a73f73fcd + languageName: node + linkType: hard + "micromark-factory-whitespace@npm:^2.0.0": version: 2.0.0 resolution: "micromark-factory-whitespace@npm:2.0.0" @@ -8691,6 +9374,15 @@ __metadata: languageName: node linkType: hard +"micromark-util-chunked@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-util-chunked@npm:1.1.0" + dependencies: + micromark-util-symbol: "npm:^1.0.0" + checksum: c435bde9110cb595e3c61b7f54c2dc28ee03e6a57fa0fc1e67e498ad8bac61ee5a7457a2b6a73022ddc585676ede4b912d28dcf57eb3bd6951e54015e14dc20b + languageName: node + linkType: hard + "micromark-util-chunked@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-chunked@npm:2.0.0" @@ -8700,6 +9392,17 @@ __metadata: languageName: node linkType: hard +"micromark-util-classify-character@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-util-classify-character@npm:1.1.0" + dependencies: + micromark-util-character: "npm:^1.0.0" + micromark-util-symbol: "npm:^1.0.0" + micromark-util-types: "npm:^1.0.0" + checksum: 8499cb0bb1f7fb946f5896285fcca65cd742f66cd3e79ba7744792bd413ec46834f932a286de650349914d02e822946df3b55d03e6a8e1d245d1ddbd5102e5b0 + languageName: node + linkType: hard + "micromark-util-classify-character@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-classify-character@npm:2.0.0" @@ -8711,6 +9414,16 @@ __metadata: languageName: node linkType: hard +"micromark-util-combine-extensions@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-util-combine-extensions@npm:1.1.0" + dependencies: + micromark-util-chunked: "npm:^1.0.0" + micromark-util-types: "npm:^1.0.0" + checksum: ee78464f5d4b61ccb437850cd2d7da4d690b260bca4ca7a79c4bb70291b84f83988159e373b167181b6716cb197e309bc6e6c96a68cc3ba9d50c13652774aba9 + languageName: node + linkType: hard + "micromark-util-combine-extensions@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-combine-extensions@npm:2.0.0" @@ -8721,6 +9434,15 @@ __metadata: languageName: node linkType: hard +"micromark-util-decode-numeric-character-reference@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-util-decode-numeric-character-reference@npm:1.1.0" + dependencies: + micromark-util-symbol: "npm:^1.0.0" + checksum: 4733fe75146e37611243f055fc6847137b66f0cde74d080e33bd26d0408c1d6f44cabc984063eee5968b133cb46855e729d555b9ff8d744652262b7b51feec73 + languageName: node + linkType: hard + "micromark-util-decode-numeric-character-reference@npm:^2.0.0": version: 2.0.1 resolution: "micromark-util-decode-numeric-character-reference@npm:2.0.1" @@ -8730,6 +9452,18 @@ __metadata: languageName: node linkType: hard +"micromark-util-decode-string@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-util-decode-string@npm:1.1.0" + dependencies: + decode-named-character-reference: "npm:^1.0.0" + micromark-util-character: "npm:^1.0.0" + micromark-util-decode-numeric-character-reference: "npm:^1.0.0" + micromark-util-symbol: "npm:^1.0.0" + checksum: f1625155db452f15aa472918499689ba086b9c49d1322a08b22bfbcabe918c61b230a3002c8bc3ea9b1f52ca7a9bb1c3dd43ccb548c7f5f8b16c24a1ae77a813 + languageName: node + linkType: hard + "micromark-util-decode-string@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-decode-string@npm:2.0.0" @@ -8742,6 +9476,13 @@ __metadata: languageName: node linkType: hard +"micromark-util-encode@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-util-encode@npm:1.1.0" + checksum: 4ef29d02b12336918cea6782fa87c8c578c67463925221d4e42183a706bde07f4b8b5f9a5e1c7ce8c73bb5a98b261acd3238fecd152e6dd1cdfa2d1ae11b60a0 + languageName: node + linkType: hard + "micromark-util-encode@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-encode@npm:2.0.0" @@ -8765,6 +9506,13 @@ __metadata: languageName: node linkType: hard +"micromark-util-html-tag-name@npm:^1.0.0": + version: 1.2.0 + resolution: "micromark-util-html-tag-name@npm:1.2.0" + checksum: ccf0fa99b5c58676dc5192c74665a3bfd1b536fafaf94723bd7f31f96979d589992df6fcf2862eba290ef18e6a8efb30ec8e1e910d9f3fc74f208871e9f84750 + languageName: node + linkType: hard + "micromark-util-html-tag-name@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-html-tag-name@npm:2.0.0" @@ -8772,6 +9520,15 @@ __metadata: languageName: node linkType: hard +"micromark-util-normalize-identifier@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-util-normalize-identifier@npm:1.1.0" + dependencies: + micromark-util-symbol: "npm:^1.0.0" + checksum: 8655bea41ffa4333e03fc22462cb42d631bbef9c3c07b625fd852b7eb442a110f9d2e5902a42e65188d85498279569502bf92f3434a1180fc06f7c37edfbaee2 + languageName: node + linkType: hard + "micromark-util-normalize-identifier@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-normalize-identifier@npm:2.0.0" @@ -8781,6 +9538,15 @@ __metadata: languageName: node linkType: hard +"micromark-util-resolve-all@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-util-resolve-all@npm:1.1.0" + dependencies: + micromark-util-types: "npm:^1.0.0" + checksum: 1ce6c0237cd3ca061e76fae6602cf95014e764a91be1b9f10d36cb0f21ca88f9a07de8d49ab8101efd0b140a4fbfda6a1efb72027ab3f4d5b54c9543271dc52c + languageName: node + linkType: hard + "micromark-util-resolve-all@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-resolve-all@npm:2.0.0" @@ -8790,6 +9556,17 @@ __metadata: languageName: node linkType: hard +"micromark-util-sanitize-uri@npm:^1.0.0": + version: 1.2.0 + resolution: "micromark-util-sanitize-uri@npm:1.2.0" + dependencies: + micromark-util-character: "npm:^1.0.0" + micromark-util-encode: "npm:^1.0.0" + micromark-util-symbol: "npm:^1.0.0" + checksum: 0d024100d95ffb88bf75f3360e305b545c1eb745430959b8633f7aa93f37ec401fc7094c90c97298409a9e30d94d53b895bae224e1bb966bea114976cfa0fd48 + languageName: node + linkType: hard + "micromark-util-sanitize-uri@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-sanitize-uri@npm:2.0.0" @@ -8801,6 +9578,18 @@ __metadata: languageName: node linkType: hard +"micromark-util-subtokenize@npm:^1.0.0": + version: 1.1.0 + resolution: "micromark-util-subtokenize@npm:1.1.0" + dependencies: + micromark-util-chunked: "npm:^1.0.0" + micromark-util-symbol: "npm:^1.0.0" + micromark-util-types: "npm:^1.0.0" + uvu: "npm:^0.5.0" + checksum: 075a1db6ea586d65827d3eead33dbfc520c4e43659c93fcd8fd82f44a7b75cfe61dcde967a3dfcc2ffd999347440ba5aa6698e65a04f3fc627e13e9f12a1a910 + languageName: node + linkType: hard + "micromark-util-subtokenize@npm:^2.0.0": version: 2.0.0 resolution: "micromark-util-subtokenize@npm:2.0.0" @@ -8827,7 +9616,7 @@ __metadata: languageName: node linkType: hard -"micromark-util-types@npm:^1.0.0": +"micromark-util-types@npm:^1.0.0, micromark-util-types@npm:^1.0.1": version: 1.1.0 resolution: "micromark-util-types@npm:1.1.0" checksum: 287ac5de4a3802bb6f6c3842197c294997a488db1c0486e03c7a8e674d9eb7720c17dda1bcb814814b8343b338c4826fcbc0555f3e75463712a60dcdb53a028e @@ -8841,6 +9630,31 @@ __metadata: languageName: node linkType: hard +"micromark@npm:^3.0.0": + version: 3.2.0 + resolution: "micromark@npm:3.2.0" + dependencies: + "@types/debug": "npm:^4.0.0" + debug: "npm:^4.0.0" + decode-named-character-reference: "npm:^1.0.0" + micromark-core-commonmark: "npm:^1.0.1" + micromark-factory-space: "npm:^1.0.0" + micromark-util-character: "npm:^1.0.0" + micromark-util-chunked: "npm:^1.0.0" + micromark-util-combine-extensions: "npm:^1.0.0" + micromark-util-decode-numeric-character-reference: "npm:^1.0.0" + micromark-util-encode: "npm:^1.0.0" + micromark-util-normalize-identifier: "npm:^1.0.0" + micromark-util-resolve-all: "npm:^1.0.0" + micromark-util-sanitize-uri: "npm:^1.0.0" + micromark-util-subtokenize: "npm:^1.0.0" + micromark-util-symbol: "npm:^1.0.0" + micromark-util-types: "npm:^1.0.1" + uvu: "npm:^0.5.0" + checksum: 560a4a501efc3859d622461aaa9345fb95b99a2f34d3d3f2a775ab04de1dd857cb0f642083a6b28ab01bd817f5f0741a1be9857fd702f45e04a3752927a66719 + languageName: node + linkType: hard + "micromark@npm:^4.0.0": version: 4.0.0 resolution: "micromark@npm:4.0.0" @@ -9086,6 +9900,13 @@ __metadata: languageName: node linkType: hard +"mri@npm:^1.1.0": + version: 1.2.0 + resolution: "mri@npm:1.2.0" + checksum: 6775a1d2228bb9d191ead4efc220bd6be64f943ad3afd4dcb3b3ac8fc7b87034443f666e38805df38e8d047b29f910c3cc7810da0109af83e42c82c73bd3f6bc + languageName: node + linkType: hard + "mrmime@npm:^2.0.0": version: 2.0.0 resolution: "mrmime@npm:2.0.0" @@ -9212,6 +10033,13 @@ __metadata: languageName: node linkType: hard +"non-layered-tidy-tree-layout@npm:^2.0.2": + version: 2.0.2 + resolution: "non-layered-tidy-tree-layout@npm:2.0.2" + checksum: 615b4da455a4ed761cc1563b126450c92f14d2d92c75cfd861fec495557a48768c5bf3012f080c8e58ecb093bfd2268a636515963a1e769f5a7029d057fa169a + languageName: node + linkType: hard + "nopt@npm:^6.0.0": version: 6.0.0 resolution: "nopt@npm:6.0.0" @@ -11033,6 +11861,13 @@ __metadata: languageName: node linkType: hard +"robust-predicates@npm:^3.0.2": + version: 3.0.2 + resolution: "robust-predicates@npm:3.0.2" + checksum: 88bd7d45a6b89e88da2631d4c111aaaf0443de4d7078e9ab7f732245790a3645cf79bf91882a9740dbc959cf56ba75d5dced5bf2259410f8b6de19fd240cd08c + languageName: node + linkType: hard + "rtl-detect@npm:^1.0.4": version: 1.0.4 resolution: "rtl-detect@npm:1.0.4" @@ -11063,6 +11898,22 @@ __metadata: languageName: node linkType: hard +"rw@npm:1": + version: 1.3.3 + resolution: "rw@npm:1.3.3" + checksum: e90985d64777a00f4ab5f8c0bfea2fb5645c6bda5238840afa339c8a4f86f776e8ce83731155643a7425a0b27ce89077dab27b2f57519996ba4d2fe54cac1941 + languageName: node + linkType: hard + +"sade@npm:^1.7.3": + version: 1.8.1 + resolution: "sade@npm:1.8.1" + dependencies: + mri: "npm:^1.1.0" + checksum: 1c67ba03c94083e0ae307ff5564ecb86c2104c0f558042fdaa40ea0054f91a63a9783f14069870f2f784336adabb70f90f22a84dc457b5a25e859aaadefe0910 + languageName: node + linkType: hard + "safe-buffer@npm:5.1.2, safe-buffer@npm:~5.1.0, safe-buffer@npm:~5.1.1": version: 5.1.2 resolution: "safe-buffer@npm:5.1.2" @@ -11770,6 +12621,13 @@ __metadata: languageName: node linkType: hard +"stylis@npm:^4.1.3": + version: 4.3.1 + resolution: "stylis@npm:4.3.1" + checksum: 20b04044397c5c69e4b9f00b037159ba82b602c61d45f26d8def08577fd6ddc4b2853d86818548c1b404d29194a99b6495cca1733880afc845533ced843cb266 + languageName: node + linkType: hard + "supports-color@npm:^5.3.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0" @@ -11978,6 +12836,13 @@ __metadata: languageName: node linkType: hard +"ts-dedent@npm:^2.2.0": + version: 2.2.0 + resolution: "ts-dedent@npm:2.2.0" + checksum: 93ed8f7878b6d5ed3c08d99b740010eede6bccfe64bce61c5a4da06a2c17d6ddbb80a8c49c2d15251de7594a4f93ffa21dd10e7be75ef66a4dc9951b4a94e2af + languageName: node + linkType: hard + "tslib@npm:^2.0.3": version: 2.4.0 resolution: "tslib@npm:2.4.0" @@ -12212,6 +13077,15 @@ __metadata: languageName: node linkType: hard +"unist-util-stringify-position@npm:^3.0.0": + version: 3.0.3 + resolution: "unist-util-stringify-position@npm:3.0.3" + dependencies: + "@types/unist": "npm:^2.0.0" + checksum: 07913e4fd77fe57d95f8b2f771354f97a29082229c1ad14ceedce6bbc77b2d784ca8296563335471cdca97915e548204bd6f098ea5b808b822b4b54087662cfb + languageName: node + linkType: hard + "unist-util-stringify-position@npm:^4.0.0": version: 4.0.0 resolution: "unist-util-stringify-position@npm:4.0.0" @@ -12369,6 +13243,29 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^9.0.0": + version: 9.0.1 + resolution: "uuid@npm:9.0.1" + bin: + uuid: dist/bin/uuid + checksum: 9d0b6adb72b736e36f2b1b53da0d559125ba3e39d913b6072f6f033e0c87835b414f0836b45bcfaf2bdf698f92297fea1c3cc19b0b258bc182c9c43cc0fab9f2 + languageName: node + linkType: hard + +"uvu@npm:^0.5.0": + version: 0.5.6 + resolution: "uvu@npm:0.5.6" + dependencies: + dequal: "npm:^2.0.0" + diff: "npm:^5.0.0" + kleur: "npm:^4.0.3" + sade: "npm:^1.7.3" + bin: + uvu: bin.js + checksum: 66ba25afc6732249877f9f4f8b6146f3aaa97538c51cf498f55825d602c33dbb903e02c7e1547cbca6bdfbb609e07eb7ea758b5156002ac2dd5072f00606f8d9 + languageName: node + linkType: hard + "value-equal@npm:^1.0.1": version: 1.0.1 resolution: "value-equal@npm:1.0.1" @@ -12454,6 +13351,13 @@ __metadata: languageName: node linkType: hard +"web-worker@npm:^1.2.0": + version: 1.3.0 + resolution: "web-worker@npm:1.3.0" + checksum: 9dd89763997a7fa4c50128bed088137775c6033cc2aead24fd82e8292991bb1d3ffc672b47df16eed86c9268d2bf230d5bb3e0d06f41a7b3c0c4c36abf4c1ba7 + languageName: node + linkType: hard + "webpack-bundle-analyzer@npm:^4.9.0": version: 4.10.1 resolution: "webpack-bundle-analyzer@npm:4.10.1" From 7431ba9b3883991b77bbf6a70bd9806090706724 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20de=20Juvigny?= <8087692+remidej@users.noreply.github.com> Date: Mon, 22 Apr 2024 14:03:52 +0200 Subject: [PATCH 3/5] fix: sanitize history versions data field (#20143) * fix: sanitize history versions data field * fix: ts cannot be named error --- .../permission/permissions-manager/sanitize.ts | 2 +- .../server/src/history/controllers/history-version.ts | 10 +++++++++- .../shared/contracts/history-versions.ts | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/core/admin/server/src/services/permission/permissions-manager/sanitize.ts b/packages/core/admin/server/src/services/permission/permissions-manager/sanitize.ts index fa1d5a5a2e..e441d7fae0 100644 --- a/packages/core/admin/server/src/services/permission/permissions-manager/sanitize.ts +++ b/packages/core/admin/server/src/services/permission/permissions-manager/sanitize.ts @@ -155,7 +155,7 @@ export default ({ action, ability, model }: any) => { traverseEntity(omitHiddenFields, ctx), // Remove not allowed fields (RBAC) traverseEntity(removeDisallowedFields(permittedFields), ctx), - // Remove roles from createdBy & updateBy fields + // Remove roles from createdBy & updatedBy fields omitCreatorRoles ); }; diff --git a/packages/core/content-manager/server/src/history/controllers/history-version.ts b/packages/core/content-manager/server/src/history/controllers/history-version.ts index 3bf50a5b63..953e56d22e 100644 --- a/packages/core/content-manager/server/src/history/controllers/history-version.ts +++ b/packages/core/content-manager/server/src/history/controllers/history-version.ts @@ -68,7 +68,15 @@ const createHistoryVersionController = ({ strapi }: { strapi: Core.Strapi }) => ...getValidPagination({ page: params.page, pageSize: params.pageSize }), }); - return { data: results, meta: { pagination } }; + return { + data: await Promise.all( + results.map(async (result) => ({ + ...result, + data: await permissionChecker.sanitizeOutput(result.data), + })) + ), + meta: { pagination }, + }; }, async restoreVersion(ctx) { diff --git a/packages/core/content-manager/shared/contracts/history-versions.ts b/packages/core/content-manager/shared/contracts/history-versions.ts index 5ce87944b2..9417571f82 100644 --- a/packages/core/content-manager/shared/contracts/history-versions.ts +++ b/packages/core/content-manager/shared/contracts/history-versions.ts @@ -16,7 +16,7 @@ export interface CreateHistoryVersion { componentsSchemas: Record<`${string}.${string}`, Struct.SchemaAttributes>; } -interface Locale { +export interface Locale { name: string; code: string; } From bdaafbbb3c33a9f4f08d44613b8fef43aea1d9dd Mon Sep 17 00:00:00 2001 From: markkaylor Date: Tue, 23 Apr 2024 10:50:47 +0200 Subject: [PATCH 4/5] chore(history): add api tests (#20157) --- .../src/history/components/VersionHeader.tsx | 13 +- .../components/tests/VersionHeader.test.tsx | 11 +- .../src/history/services/historyVersion.ts | 46 +- .../__tests__/history-version.test.ts | 199 -------- .../history/controllers/history-version.ts | 4 +- .../server/src/history/services/history.ts | 10 +- .../shared/contracts/history-versions.ts | 1 - .../history/history.test.api.ts | 442 ++++++++++++++++++ 8 files changed, 495 insertions(+), 231 deletions(-) delete mode 100644 packages/core/content-manager/server/src/history/controllers/__tests__/history-version.test.ts create mode 100644 tests/api/core/content-manager/content-manager/history/history.test.api.ts diff --git a/packages/core/content-manager/admin/src/history/components/VersionHeader.tsx b/packages/core/content-manager/admin/src/history/components/VersionHeader.tsx index 9830db9f7f..382bae4af1 100644 --- a/packages/core/content-manager/admin/src/history/components/VersionHeader.tsx +++ b/packages/core/content-manager/admin/src/history/components/VersionHeader.tsx @@ -45,18 +45,18 @@ export const VersionHeader = ({ headerId }: VersionHeaderProps) => { const mainFieldValue = version.data[mainField]; - const getBackLink = (): To => { + const getNextNavigation = (): To => { const pluginsQueryParams = stringify({ plugins: query.plugins }, { encode: false }); if (collectionType === COLLECTION_TYPES) { return { - pathname: `../${collectionType}/${version.contentType}/${version.relatedDocumentId}`, + pathname: `/content-manager/${collectionType}/${version.contentType}/${version.relatedDocumentId}`, search: pluginsQueryParams, }; } return { - pathname: `../${collectionType}/${version.contentType}`, + pathname: `/content-manager/${collectionType}/${version.contentType}`, search: pluginsQueryParams, }; }; @@ -64,16 +64,17 @@ export const VersionHeader = ({ headerId }: VersionHeaderProps) => { const handleRestore = async () => { try { const response = await restoreVersion({ + documentId: version.relatedDocumentId, + collectionType, params: { versionId: version.id, - documentId: version.relatedDocumentId, contentType: version.contentType, }, body: { contentType: version.contentType }, }); if ('data' in response) { - navigate(`/content-manager/${collectionType}/${slug}/${response.data.data?.documentId}`); + navigate(getNextNavigation()); toggleNotification({ type: 'success', @@ -137,7 +138,7 @@ export const VersionHeader = ({ headerId }: VersionHeaderProps) => { startIcon={} as={NavLink} // @ts-expect-error - types are not inferred correctly through the as prop. - to={getBackLink()} + to={getNextNavigation()} > {formatMessage({ id: 'global.back', diff --git a/packages/core/content-manager/admin/src/history/components/tests/VersionHeader.test.tsx b/packages/core/content-manager/admin/src/history/components/tests/VersionHeader.test.tsx index 2d281caa9f..8c1bce3ebb 100644 --- a/packages/core/content-manager/admin/src/history/components/tests/VersionHeader.test.tsx +++ b/packages/core/content-manager/admin/src/history/components/tests/VersionHeader.test.tsx @@ -80,7 +80,7 @@ describe('VersionHeader', () => { const backLink = screen.getByRole('link', { name: 'Back' }); expect(backLink).toHaveAttribute( 'href', - '/collection-types/api::kitchensink.kitchensink/pcwmq3rlmp5w0be3cuplhnpr' + '/content-manager/collection-types/api::kitchensink.kitchensink/pcwmq3rlmp5w0be3cuplhnpr' ); }); @@ -111,7 +111,7 @@ describe('VersionHeader', () => { const backLink = screen.getByRole('link', { name: 'Back' }); expect(backLink).toHaveAttribute( 'href', - '/collection-types/api::kitchensink.kitchensink/pcwmq3rlmp5w0be3cuplhnpr?plugins[i18n][locale]=en' + '/content-manager/collection-types/api::kitchensink.kitchensink/pcwmq3rlmp5w0be3cuplhnpr?plugins[i18n][locale]=en' ); }); @@ -158,7 +158,10 @@ describe('VersionHeader', () => { expect(await screen.findByText('Test Title (homepage)')).toBeInTheDocument(); const backLink = screen.getByRole('link', { name: 'Back' }); - expect(backLink).toHaveAttribute('href', '/single-types/api::homepage.homepage'); + expect(backLink).toHaveAttribute( + 'href', + '/content-manager/single-types/api::homepage.homepage' + ); }); it('should display the correct title and subtitle for a localized entry', async () => { @@ -185,7 +188,7 @@ describe('VersionHeader', () => { const backLink = screen.getByRole('link', { name: 'Back' }); expect(backLink).toHaveAttribute( 'href', - '/single-types/api::homepage.homepage?plugins[i18n][locale]=en' + '/content-manager/single-types/api::homepage.homepage?plugins[i18n][locale]=en' ); }); }); diff --git a/packages/core/content-manager/admin/src/history/services/historyVersion.ts b/packages/core/content-manager/admin/src/history/services/historyVersion.ts index c54e7bdbb4..6c780a22bb 100644 --- a/packages/core/content-manager/admin/src/history/services/historyVersion.ts +++ b/packages/core/content-manager/admin/src/history/services/historyVersion.ts @@ -1,9 +1,17 @@ +import { Data } from '@strapi/types'; + import { GetHistoryVersions, RestoreHistoryVersion, } from '../../../../shared/contracts/history-versions'; +import { COLLECTION_TYPES } from '../../constants/collections'; import { contentManagerApi } from '../../services/api'; +interface RestoreVersion extends RestoreHistoryVersion.Request { + documentId: Data.ID; + collectionType?: string; +} + const historyVersionsApi = contentManagerApi.injectEndpoints({ endpoints: (builder) => ({ getHistoryVersions: builder.query< @@ -21,23 +29,27 @@ const historyVersionsApi = contentManagerApi.injectEndpoints({ }, providesTags: ['HistoryVersion'], }), - restoreVersion: builder.mutation( - { - query({ params, body }) { - return { - url: `/content-manager/history-versions/${params.versionId}/restore`, - method: 'PUT', - data: body, - }; - }, - invalidatesTags: (_res, _error, { params }) => { - return [ - 'HistoryVersion', - { type: 'Document', id: `${params.contentType}_${params.documentId}` }, - ]; - }, - } - ), + restoreVersion: builder.mutation({ + query({ params, body }) { + return { + url: `/content-manager/history-versions/${params.versionId}/restore`, + method: 'PUT', + data: body, + }; + }, + invalidatesTags: (_res, _error, { documentId, collectionType, params }) => { + return [ + 'HistoryVersion', + { + type: 'Document', + id: + collectionType === COLLECTION_TYPES + ? `${params.contentType}_${documentId}` + : params.contentType, + }, + ]; + }, + }), }), }); diff --git a/packages/core/content-manager/server/src/history/controllers/__tests__/history-version.test.ts b/packages/core/content-manager/server/src/history/controllers/__tests__/history-version.test.ts deleted file mode 100644 index 465f079113..0000000000 --- a/packages/core/content-manager/server/src/history/controllers/__tests__/history-version.test.ts +++ /dev/null @@ -1,199 +0,0 @@ -import { createHistoryVersionController } from '../history-version'; - -const mockFindVersionsPage = jest.fn(); - -// History utils -jest.mock('../../utils', () => ({ - getService: jest.fn((_strapi, name) => { - if (name === 'history') { - return { - findVersionsPage: mockFindVersionsPage, - }; - } - }), -})); - -// Content Manager utils -jest.mock('../../../utils', () => ({ - getService: jest.fn((name) => { - if (name === 'permission-checker') { - return { - create: jest.fn(() => ({ - cannot: { - read: jest.fn(() => false), - }, - sanitizeQuery: jest.fn((query) => query), - })), - }; - } - }), -})); - -describe('History version controller', () => { - beforeEach(() => { - mockFindVersionsPage.mockClear(); - }); - - describe('findMany', () => { - it('should require contentType and documentId for collection types', () => { - const ctx = { - state: { - userAbility: {}, - }, - query: {}, - }; - - const historyVersionController = createHistoryVersionController({ - // @ts-expect-error - we're not mocking the entire strapi object - strapi: { getModel: jest.fn(() => ({ kind: 'collectionType' })) }, - }); - - // @ts-expect-error partial context - expect(historyVersionController.findMany(ctx)).rejects.toThrow( - /contentType and documentId are required/ - ); - expect(mockFindVersionsPage).not.toHaveBeenCalled(); - }); - - it('should require contentType for single types', () => { - const ctx = { - state: { - userAbility: {}, - }, - query: {}, - }; - - const historyVersionController = createHistoryVersionController({ - // @ts-expect-error - we're not mocking the entire strapi object - strapi: { getModel: jest.fn(() => ({ kind: 'singleType' })) }, - }); - - // @ts-expect-error partial context - expect(historyVersionController.findMany(ctx)).rejects.toThrow(/contentType is required/); - expect(mockFindVersionsPage).not.toHaveBeenCalled(); - }); - }); - - it('should call findVersionsPage for collection types', async () => { - const ctx = { - state: { - userAbility: {}, - }, - query: { - documentId: 'document-id', - contentType: 'api::test.test', - }, - }; - - mockFindVersionsPage.mockResolvedValueOnce({ - results: [{ id: 'history-version-id' }], - pagination: { - page: 1, - pageSize: 20, - pageCount: 1, - total: 0, - }, - }); - - const historyVersionController = createHistoryVersionController({ - // @ts-expect-error - we're not mocking the entire strapi object - strapi: { getModel: jest.fn(() => ({ kind: 'collectionType' })) }, - }); - - // @ts-expect-error partial context - const response = await historyVersionController.findMany(ctx); - - expect(mockFindVersionsPage).toHaveBeenCalled(); - expect(response.data.length).toBe(1); - expect(response.meta.pagination).toBeDefined(); - }); - - it('should call findVersionsPage for single types', async () => { - const ctx = { - state: { - userAbility: {}, - }, - query: { - contentType: 'api::test.test', - }, - }; - - mockFindVersionsPage.mockResolvedValueOnce({ - results: [{ id: 'history-version-id' }], - pagination: { - page: 1, - pageSize: 20, - pageCount: 1, - total: 0, - }, - }); - - const historyVersionController = createHistoryVersionController({ - // @ts-expect-error - we're not mocking the entire strapi object - strapi: { getModel: jest.fn(() => ({ kind: 'singleType' })) }, - }); - - // @ts-expect-error partial context - const response = await historyVersionController.findMany(ctx); - - expect(mockFindVersionsPage).toHaveBeenCalled(); - expect(response.data.length).toBe(1); - expect(response.meta.pagination).toBeDefined(); - }); - - it('applies pagination params', async () => { - const ctx = { - state: { - userAbility: {}, - }, - query: { - contentType: 'api::test.test', - }, - }; - - const historyVersionController = createHistoryVersionController({ - // @ts-expect-error - we're not mocking the entire strapi object - strapi: { getModel: jest.fn(() => ({ kind: 'singleType' })) }, - }); - - /** - * Applies default pagination params - */ - mockFindVersionsPage.mockResolvedValueOnce({ - results: [], - pagination: { - page: 1, - pageSize: 20, - }, - }); - // @ts-expect-error partial context - const mockResponse = await historyVersionController.findMany(ctx); - expect(mockFindVersionsPage).toHaveBeenCalledWith( - expect.objectContaining({ - page: 1, - pageSize: 20, - }) - ); - expect(mockResponse.meta.pagination.page).toBe(1); - expect(mockResponse.meta.pagination.pageSize).toBe(20); - - /** - * Prevents invalid pagination params - */ - mockFindVersionsPage.mockResolvedValueOnce({ - results: [], - pagination: {}, - }); - // @ts-expect-error partial context - await historyVersionController.findMany({ - ...ctx, - query: { ...ctx.query, page: '-1', pageSize: '1000' }, - }); - expect(mockFindVersionsPage).toHaveBeenCalledWith( - expect.objectContaining({ - page: 1, - pageSize: 20, - }) - ); - }); -}); diff --git a/packages/core/content-manager/server/src/history/controllers/history-version.ts b/packages/core/content-manager/server/src/history/controllers/history-version.ts index 953e56d22e..4e0e6ce042 100644 --- a/packages/core/content-manager/server/src/history/controllers/history-version.ts +++ b/packages/core/content-manager/server/src/history/controllers/history-version.ts @@ -37,13 +37,13 @@ const createHistoryVersionController = ({ strapi }: { strapi: Core.Strapi }) => return { async findMany(ctx) { const contentTypeUid = ctx.query.contentType as UID.ContentType; - const isSingleType = strapi.getModel(contentTypeUid).kind === 'singleType'; + const isSingleType = strapi.getModel(contentTypeUid)?.kind === 'singleType'; if (isSingleType && !contentTypeUid) { throw new errors.ForbiddenError('contentType is required'); } - if (!contentTypeUid && !ctx.query.documentId) { + if (!isSingleType && (!contentTypeUid || !ctx.query.documentId)) { throw new errors.ForbiddenError('contentType and documentId are required'); } diff --git a/packages/core/content-manager/server/src/history/services/history.ts b/packages/core/content-manager/server/src/history/services/history.ts index 730a716853..75ba2cc309 100644 --- a/packages/core/content-manager/server/src/history/services/history.ts +++ b/packages/core/content-manager/server/src/history/services/history.ts @@ -47,6 +47,9 @@ const createHistoryService = ({ strapi }: { strapi: Core.Strapi }) => { }; const localesService = strapi.plugin('i18n')?.service('locales'); + + const getDefaultLocale = async () => (localesService ? localesService.getDefaultLocale() : null); + const getLocaleDictionary = async () => { if (!localesService) return {}; @@ -163,8 +166,9 @@ const createHistoryService = ({ strapi }: { strapi: Core.Strapi }) => { ? { documentId: result.documentId, locale: context.params?.locale } : { documentId: context.params.documentId, locale: context.params?.locale }; - const defaultLocale = localesService ? await localesService.getDefaultLocale() : null; + const defaultLocale = await getDefaultLocale(); const locale = documentContext.locale || defaultLocale; + const document = await strapi.documents(contentTypeUid).findOne({ documentId: documentContext.documentId, locale, @@ -251,6 +255,7 @@ const createHistoryService = ({ strapi }: { strapi: Core.Strapi }) => { results: HistoryVersions.HistoryVersionDataResponse[]; pagination: HistoryVersions.Pagination; }> { + const locale = params.locale || (await getDefaultLocale()); const [{ results, pagination }, localeDictionary] = await Promise.all([ query.findPage({ ...params, @@ -258,7 +263,7 @@ const createHistoryService = ({ strapi }: { strapi: Core.Strapi }) => { $and: [ { contentType: params.contentType }, ...(params.documentId ? [{ relatedDocumentId: params.documentId }] : []), - ...(params.locale ? [{ locale: params.locale }] : []), + ...(locale ? [{ locale }] : []), ], }, populate: ['createdBy'], @@ -497,6 +502,7 @@ const createHistoryService = ({ strapi }: { strapi: Core.Strapi }) => { const data = omit(['id', ...Object.keys(schemaDiff.removed)], dataWithoutMissingRelations); const restoredDocument = await strapi.documents(version.contentType).update({ documentId: version.relatedDocumentId, + locale: version.locale, data, }); diff --git a/packages/core/content-manager/shared/contracts/history-versions.ts b/packages/core/content-manager/shared/contracts/history-versions.ts index 9417571f82..e724e88a1c 100644 --- a/packages/core/content-manager/shared/contracts/history-versions.ts +++ b/packages/core/content-manager/shared/contracts/history-versions.ts @@ -82,7 +82,6 @@ export declare namespace RestoreHistoryVersion { export interface Request { params: { versionId: Data.ID; - documentId: Data.ID; contentType: UID.ContentType; }; body: { diff --git a/tests/api/core/content-manager/content-manager/history/history.test.api.ts b/tests/api/core/content-manager/content-manager/history/history.test.api.ts new file mode 100644 index 0000000000..da4deff607 --- /dev/null +++ b/tests/api/core/content-manager/content-manager/history/history.test.api.ts @@ -0,0 +1,442 @@ +import { createStrapiInstance } from 'api-tests/strapi'; +import { createAuthRequest } from 'api-tests/request'; +import { createUtils, describeOnCondition } from 'api-tests/utils'; +import { createTestBuilder } from 'api-tests/builder'; + +const edition = process.env.STRAPI_DISABLE_EE === 'true' ? 'CE' : 'EE'; + +const collectionTypeUid = 'api::product.product'; +const collectionTypeModel = { + draftAndPublish: true, + singularName: 'product', + pluralName: 'products', + displayName: 'Product', + kind: 'collectionType', + pluginOptions: { + i18n: { + localized: true, + }, + }, + attributes: { + name: { + type: 'string', + pluginOptions: { + i18n: { + localized: true, + }, + }, + }, + description: { + type: 'string', + pluginOptions: { + i18n: { + localized: true, + }, + }, + }, + }, +}; + +const singleTypeUid = 'api::homepage.homepage'; +const singleTypeModel = { + draftAndPublish: true, + singularName: 'homepage', + pluralName: 'homepages', + displayName: 'Homepage', + kind: 'singleType', + pluginOptions: { + i18n: { + localized: true, + }, + }, + attributes: { + title: { + type: 'string', + pluginOptions: { + i18n: { + localized: true, + }, + }, + }, + subtitle: { + type: 'string', + pluginOptions: { + i18n: { + localized: true, + }, + }, + }, + }, +}; + +interface CreateEntryArgs { + uid: string; + data: Record; + isCollectionType?: boolean; +} + +interface UpdateEntryArgs extends CreateEntryArgs { + documentId?: string; + locale?: string; +} + +describeOnCondition(edition === 'EE')('History API', () => { + const builder = createTestBuilder(); + let strapi; + let rq; + let collectionTypeDocumentId; + let singleTypeDocumentId; + + const createEntry = async ({ uid, data, isCollectionType = true }: CreateEntryArgs) => { + const type = isCollectionType ? 'collection-types' : 'single-types'; + + const { body } = await rq({ + method: 'POST', + url: `/content-manager/${type}/${uid}`, + body: data, + }); + + return body; + }; + + const updateEntry = async ({ uid, documentId, data, locale }: UpdateEntryArgs) => { + const type = documentId ? 'collection-types' : 'single-types'; + const params = documentId ? `${type}/${uid}/${documentId}` : `${type}/${uid}`; + + const { body } = await rq({ + method: 'PUT', + url: `/content-manager/${params}`, + body: data, + qs: { locale }, + }); + + return body; + }; + + const createUserAndReq = async ( + userName: string, + permissions: { action: string; subject: string }[] + ) => { + const utils = createUtils(strapi); + const role = await utils.createRole({ + name: `role-${userName}`, + description: `Role with restricted permissions for ${userName}`, + }); + + const rolePermissions = await utils.assignPermissionsToRole(role.id, permissions); + Object.assign(role, { permissions: rolePermissions }); + + const user = await utils.createUser({ + firstname: userName, + lastname: 'User', + email: `${userName}.user@strapi.io`, + roles: [role.id], + }); + + const rq = await createAuthRequest({ strapi, userInfo: user }); + + return rq; + }; + + beforeAll(async () => { + await builder.addContentTypes([collectionTypeModel, singleTypeModel]).build(); + + strapi = await createStrapiInstance(); + rq = await createAuthRequest({ strapi }); + + // Create another locale + const localeService = strapi.plugin('i18n').service('locales'); + await localeService.create({ code: 'fr', name: 'French' }); + + // Create a collection type to create an initial history version + const collectionType = await createEntry({ + uid: collectionTypeUid, + data: { + name: 'Product 1', + }, + }); + + // Update the single type to create an initial history version + const singleType = await updateEntry({ + uid: singleTypeUid, + data: { + title: 'Welcome', + }, + isCollectionType: false, + }); + // Set the documentIds to test + collectionTypeDocumentId = collectionType.data.documentId; + singleTypeDocumentId = singleType.data.documentId; + + // Update to create history versions for entries in different locales + await Promise.all([ + updateEntry({ + documentId: collectionTypeDocumentId, + uid: collectionTypeUid, + data: { + description: 'Hello', + }, + }), + updateEntry({ + documentId: collectionTypeDocumentId, + uid: collectionTypeUid, + locale: 'fr', + data: { + name: 'Produit 1', + }, + }), + updateEntry({ + documentId: collectionTypeDocumentId, + uid: collectionTypeUid, + locale: 'fr', + data: { + description: 'Coucou', + }, + }), + updateEntry({ + uid: singleTypeUid, + data: { + description: 'Wow, amazing!', + }, + isCollectionType: false, + }), + updateEntry({ + uid: singleTypeUid, + data: { + title: 'Bienvenue', + }, + isCollectionType: false, + locale: 'fr', + }), + updateEntry({ + uid: singleTypeUid, + data: { + description: 'Super', + }, + isCollectionType: false, + locale: 'fr', + }), + ]); + }); + + afterAll(async () => { + await strapi.destroy(); + await builder.cleanup(); + }); + + describe('Find many history versions', () => { + test('A collection type throws with invalid query params', async () => { + const noDocumentId = await rq({ + method: 'GET', + url: `/content-manager/history-versions/?contentType=${collectionTypeUid}`, + }); + + const noContentTypeUid = await rq({ + method: 'GET', + url: `/content-manager/history-versions/?documentId=${collectionTypeDocumentId}`, + }); + + expect(noDocumentId.statusCode).toBe(403); + expect(noContentTypeUid.statusCode).toBe(403); + }); + + test('A single type throws with invalid query params', async () => { + const singleTypeNoContentTypeUid = await rq({ + method: 'GET', + url: `/content-manager/history-versions/`, + }); + + expect(singleTypeNoContentTypeUid.statusCode).toBe(403); + }); + + test('Throws without read permissions', async () => { + const restrictedRq = await createUserAndReq('restricted', []); + const res = await restrictedRq({ + method: 'GET', + url: `/content-manager/history-versions/?contentType=${collectionTypeUid}&documentId=${collectionTypeDocumentId}`, + }); + + expect(res.statusCode).toBe(403); + }); + + test('A collection type finds many versions in the default locale', async () => { + const collectionType = await rq({ + method: 'GET', + url: `/content-manager/history-versions/?contentType=${collectionTypeUid}&documentId=${collectionTypeDocumentId}`, + }); + + expect(collectionType.statusCode).toBe(200); + expect(collectionType.body.data).toHaveLength(2); + expect(collectionType.body.data[0].relatedDocumentId).toBe(collectionTypeDocumentId); + expect(collectionType.body.data[1].relatedDocumentId).toBe(collectionTypeDocumentId); + expect(collectionType.body.data[0].locale.code).toBe('en'); + expect(collectionType.body.data[1].locale.code).toBe('en'); + expect(collectionType.body.meta.pagination).toEqual({ + page: 1, + pageSize: 20, + pageCount: 1, + total: 2, + }); + }); + + test('A collection type finds many versions in the provided locale', async () => { + const collectionType = await rq({ + method: 'GET', + url: `/content-manager/history-versions/?contentType=${collectionTypeUid}&documentId=${collectionTypeDocumentId}&locale=fr`, + }); + + expect(collectionType.statusCode).toBe(200); + expect(collectionType.body.data).toHaveLength(2); + expect(collectionType.body.data[0].relatedDocumentId).toBe(collectionTypeDocumentId); + expect(collectionType.body.data[1].relatedDocumentId).toBe(collectionTypeDocumentId); + expect(collectionType.body.data[0].locale.code).toBe('fr'); + expect(collectionType.body.data[1].locale.code).toBe('fr'); + expect(collectionType.body.meta.pagination).toEqual({ + page: 1, + pageSize: 20, + pageCount: 1, + total: 2, + }); + }); + + test('A single type finds many versions in the default locale', async () => { + const singleType = await rq({ + method: 'GET', + url: `/content-manager/history-versions/?contentType=${singleTypeUid}`, + }); + + expect(singleType.statusCode).toBe(200); + expect(singleType.body.data).toHaveLength(2); + expect(singleType.body.data[0].relatedDocumentId).toBe(singleTypeDocumentId); + expect(singleType.body.data[1].relatedDocumentId).toBe(singleTypeDocumentId); + expect(singleType.body.data[0].locale.code).toBe('en'); + expect(singleType.body.data[1].locale.code).toBe('en'); + expect(singleType.body.meta.pagination).toEqual({ + page: 1, + pageSize: 20, + pageCount: 1, + total: 2, + }); + }); + + test('A single type finds many versions in the provided locale', async () => { + const singleType = await rq({ + method: 'GET', + url: `/content-manager/history-versions/?contentType=${singleTypeUid}&locale=fr`, + }); + + expect(singleType.statusCode).toBe(200); + expect(singleType.body.data).toHaveLength(2); + expect(singleType.body.data[0].relatedDocumentId).toBe(singleTypeDocumentId); + expect(singleType.body.data[1].relatedDocumentId).toBe(singleTypeDocumentId); + expect(singleType.body.data[0].locale.code).toBe('fr'); + expect(singleType.body.data[1].locale.code).toBe('fr'); + expect(singleType.body.meta.pagination).toEqual({ + page: 1, + pageSize: 20, + pageCount: 1, + total: 2, + }); + }); + + test('Applies pagination params', async () => { + const collectionType = await rq({ + method: 'GET', + url: `/content-manager/history-versions/?contentType=${collectionTypeUid}&documentId=${collectionTypeDocumentId}&page=1&pageSize=1`, + }); + + expect(collectionType.body.data).toHaveLength(1); + expect(collectionType.body.meta.pagination).toEqual({ + page: 1, + pageSize: 1, + pageCount: 2, + total: 2, + }); + }); + }); + + describe('Restore a history version', () => { + test('Throws with invalid body', async () => { + const res = await rq({ + method: 'PUT', + url: `/content-manager/history-versions/1/restore`, + body: {}, + }); + + expect(res.statusCode).toBe(400); + expect(res.body).toMatchObject({ + data: null, + error: { + status: 400, + name: 'ValidationError', + message: 'contentType is required', + }, + }); + }); + + test('Throws without update permissions', async () => { + const restrictedRq = await createUserAndReq('read', [ + { action: 'plugin::content-manager.explorer.read', subject: collectionTypeUid }, + ]); + const res = await restrictedRq({ + method: 'PUT', + url: `/content-manager/history-versions/1/restore`, + body: { + contentType: collectionTypeUid, + }, + }); + + expect(res.statusCode).toBe(403); + expect(res.body).toMatchObject({ + data: null, + error: { + status: 403, + name: 'ForbiddenError', + message: 'Forbidden', + }, + }); + }); + + test('Restores a history version in the default locale', async () => { + const currentDocument = await strapi + .documents(collectionTypeUid) + .findOne({ documentId: collectionTypeDocumentId }); + + await rq({ + method: 'PUT', + url: `/content-manager/history-versions/1/restore`, + body: { + contentType: collectionTypeUid, + }, + }); + + const restoredDocument = await strapi + .documents(collectionTypeUid) + .findOne({ documentId: collectionTypeDocumentId }); + + expect(currentDocument.description).toBe('Hello'); + expect(restoredDocument.description).toBe(null); + }); + + test('Restores a history version in the provided locale', async () => { + const currentDocument = await strapi + .documents(collectionTypeUid) + .findOne({ documentId: collectionTypeDocumentId, locale: 'fr' }); + + await rq({ + method: 'PUT', + url: `/content-manager/history-versions/4/restore`, + body: { + contentType: collectionTypeUid, + }, + }); + + const restoredDocument = await strapi + .documents(collectionTypeUid) + .findOne({ documentId: collectionTypeDocumentId, locale: 'fr' }); + + expect(currentDocument.description).toBe('Coucou'); + expect(restoredDocument.description).toBe(null); + }); + }); +}); From 01bf5cfbe41cfdfee9df1713708e401c802d2f6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-S=C3=A9bastien=20Herbaux?= Date: Tue, 23 Apr 2024 18:19:37 +0200 Subject: [PATCH 5/5] docs: update code formatting and minor text adjustments in type system documentation (#20186) --- .../05-type-system/02-concepts/01-schema.mdx | 19 ++-- .../05-type-system/02-concepts/02-uid.mdx | 18 ++-- .../02-concepts/03-public-registry.mdx | 87 ++++++++++--------- 3 files changed, 69 insertions(+), 55 deletions(-) diff --git a/docs/docs/guides/05-type-system/02-concepts/01-schema.mdx b/docs/docs/guides/05-type-system/02-concepts/01-schema.mdx index 115ad857ca..a5a76d78e8 100644 --- a/docs/docs/guides/05-type-system/02-concepts/01-schema.mdx +++ b/docs/docs/guides/05-type-system/02-concepts/01-schema.mdx @@ -67,8 +67,8 @@ For instance, a string attribute will resolve to a primitive string in an entity ### Usage -import Tabs from '@theme/Tabs' -import TabItem from '@theme/TabItem' +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; @@ -89,22 +89,23 @@ declare const component: Schema.Component; declare function processAnySchema(schema: Schema.Schema): void; -processAnySchema(schema); // ✅ +processAnySchema(schema); // ✅ processAnySchema(contentType); // ✅ processAnySchema(component); // ✅ declare function processContentTypeSchema(schema: Schema.ContentType): void; -processContentTypeSchema(schema); // ✅ +processContentTypeSchema(schema); // ✅ processContentTypeSchema(contentType); // ✅ processContentTypeSchema(component); // ❌ Error, a component schema is not assignable to a content-type schema declare function processComponentSchema(schema: Schema.Component): void; -processComponentSchema(schema); // ✅ +processComponentSchema(schema); // ✅ processComponentSchema(contentType); // ❌ Error, a content-type schema is not assignable to a component schema processComponentSchema(component); // ✅ ``` + Schema definitions exported from the `Struct` namespace defines the low level type representation of Strapi schemas. @@ -112,6 +113,7 @@ Schema definitions exported from the `Struct` namespace defines the low level ty :::caution Those types can be useful when you want to validate other types against the base ones, but realistically, the public Schema types should almost always be preferred. ::: + ```typescript import type { Struct } from '@strapi/strapi'; @@ -121,21 +123,22 @@ declare const component: Struct.ComponentSchema; declare function processAnySchema(schema: Struct.Schema): void; -processAnySchema(schema); // ✅ +processAnySchema(schema); // ✅ processAnySchema(contentType); // ✅ processAnySchema(component); // ✅ declare function processContentTypeSchema(schema: Struct.ContentTypeSchema): void; -processContentTypeSchema(schema); // ✅ +processContentTypeSchema(schema); // ✅ processContentTypeSchema(contentType); // ✅ processContentTypeSchema(component); // ❌ Error, a component schema is not assignable to a content-type schema declare function processComponentSchema(schema: Struct.ComponentSchema): void; -processComponentSchema(schema); // ✅ +processComponentSchema(schema); // ✅ processComponentSchema(contentType); // ❌ Error, a content-type schema is not assignable to a component schema processComponentSchema(component); // ✅ ``` + diff --git a/docs/docs/guides/05-type-system/02-concepts/02-uid.mdx b/docs/docs/guides/05-type-system/02-concepts/02-uid.mdx index db2692423d..6d26d1c1ee 100644 --- a/docs/docs/guides/05-type-system/02-concepts/02-uid.mdx +++ b/docs/docs/guides/05-type-system/02-concepts/02-uid.mdx @@ -14,7 +14,6 @@ On this page, **a resource** is considered as **anything that can be identified This includes (but is not limited to) controllers, schema, services, policies, middlewares, etc... ::: - In the Type System, UIDs play a crucial role in referencing various resources (such as schema and entities) by attaching a unique identifier. To put it simply, a UID is a unique (string) literal key used to identify, locate, or access a particular resource within the system. @@ -26,6 +25,7 @@ This makes it the perfect tool to index type registries or to use as a type para ### Format A UID is composed of 3 different parts: + 1. A namespace ([link](#1-namespaces)) 2. A separator ([link](#2-separators)) 3. A name ([link](#3-names)) @@ -46,7 +46,7 @@ Scoped namespaces are defined by a base name, followed by a separator (`::`) and In Strapi there are two of them: | Name | Definition | Description | -|--------|:-----------------:|------------------------------------------------------| +| ------ | :---------------: | ---------------------------------------------------- | | API | `api::` | Represent a resource present in the `` API | | Plugin | `plugin::` | Represent a resource present in the `` plugin | @@ -57,7 +57,7 @@ These namespaces are used as a simple prefix and define the origin of a resource Strapi uses three of them to create UIDs | Name | Definition | Description | -|--------|:----------:|-------------------------------------------------------------------------------| +| ------ | :--------: | ----------------------------------------------------------------------------- | | Strapi | `strapi` | Represent a resource present in the core of strapi | | Admin | `admin` | Represent a resource present in Strapi admin | | Global | `global` | Rarely used (_e.g. policies or middlewares_), it represents a global resource | @@ -90,7 +90,7 @@ ContentType and Component are referring to both the related schema and entity. ::: | | ContentType | Component | Middleware | Policy | Controller | Service | -|--------------------------|:------------------:|:------------------:|:------------------:|:------------------:|:------------------:|:------------------:| +| ------------------------ | :----------------: | :----------------: | :----------------: | :----------------: | :----------------: | :----------------: | | `api::.` | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | `plugin::.` | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | | `.` | :x: | :white_check_mark: | :x: | :x: | :x: | :x: | @@ -141,6 +141,7 @@ fetch('api::article.article'); fetch('admin::user'); // ^ this should return a Data.Entity<'admin::user'> ``` + To do that, we'll need the function to be able to provide us with the current `uid` type based on usage. ```typescript @@ -171,14 +172,15 @@ Let's add the possibility to select which fields we want to return for our entit ```typescript import type { UID, Data, Schema } from '@strapi/types'; -declare function fetch< - T extends UID.ContentType, - F extends Schema.AttributeNames ->(uid: T, fields: F[]): Data.ContentType; +declare function fetch>( + uid: T, + fields: F[] +): Data.ContentType; ``` :::tip You may have noticed that we're using the inferred UID type (`T`) to reference both: + - An entity (`Data.Entity`) - A schema (`Schema.AttributeNames`) diff --git a/docs/docs/guides/05-type-system/02-concepts/03-public-registry.mdx b/docs/docs/guides/05-type-system/02-concepts/03-public-registry.mdx index 3ee780a471..572eaaadf2 100644 --- a/docs/docs/guides/05-type-system/02-concepts/03-public-registry.mdx +++ b/docs/docs/guides/05-type-system/02-concepts/03-public-registry.mdx @@ -9,8 +9,8 @@ tags: toc_max_heading_level: 5 --- -import Tabs from '@theme/Tabs' -import TabItem from '@theme/TabItem' +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; ### Context @@ -108,6 +108,7 @@ Creating a new registry is as simple as exporting an indexed interface from the Let's declare the content-type schema registry. It should accept: + - Content-type UIDs as keys - Content-type schemas as values @@ -130,7 +131,7 @@ We use low level types to define our index (`Internal`/`Struct`) to keep it as g To define `UID.ContentType`, we extract every key (`Internal.Registry.Keys`) from the public content-type registry (`Public.ContentTypeSchemas`) that matches with the base definition of a content-type UID (`Internal.UID.ContentType`). ```ts title="@strapi/types/uid.ts" -import type { Internal, Public } from '@strapi/types' +import type { Internal, Public } from '@strapi/types'; export type ContentType = Internal.Registry.Keys< Public.ContentTypeSchemas, @@ -151,7 +152,7 @@ Since `UID.ContentType` (`TUID`) is [dynamically built based on actual keys](#ui ::: ```ts title="@strapi/types/schema.ts" -import type { UID, Public } from '@strapi/types' +import type { UID, Public } from '@strapi/types'; export type ContentType = Public.ContentTypeSchemas[TUID]; ``` @@ -172,6 +173,7 @@ Remember to use dynamic type definitions (`UID`, `Data`, `Schema`) and not stati :::info[Reminder] Registries are **indexed**, which means that: + - **When augmented** (_e.g. in users' applications_), they'll return **strongly typed values** that correspond to the defined types. - **When empty** (_e.g. in Strapi codebase_), they'll return **generic low level types** based on their index definition. @@ -183,6 +185,7 @@ Registries are **indexed**, which means that: const uid: UID.ContentType; // ^ 'api::article.article' | 'admin::user' ``` + ```ts @@ -191,6 +194,7 @@ Registries are **indexed**, which means that: const uid: UID.ContentType; // ^ `admin::${string}` | `api::${string}.${string}` | `plugin::${string}.${string}` | `strapi::${string}` ``` + ::: @@ -215,7 +219,7 @@ declare module '@strapi/strapi' { } } } -```` +``` This will force every type that depends on the `Public.ContentTypeSchemas` registry to recognize `'api::article.article'` as the only valid UID and `ApiArticleArticle` the only valid schema. @@ -235,18 +239,20 @@ The process will generate type definitions based on the user application state ( Generate the types once. - ```shell title="my-app/" - yarn strapi ts:generate-types - ``` +```shell title="my-app/" +yarn strapi ts:generate-types +``` + Start the application in dev mode, and generate types on every server restart. Useful when working with the content-type builder. - ```shell title="my-app/" - yarn develop - ``` +```shell title="my-app/" +yarn develop +``` + @@ -277,40 +283,43 @@ declare module '@strapi/strapi' { When coupling everything together, the end result is a TypeScript developer experience automatically adjusted to the current context. + - - ```ts title="my-app/src/index.ts" - export default () => ({ - bootstrap() { - strapi.findOne('ap'); - // ^ TypeScript will autocomplete with "api::article.article" + +```ts title="my-app/src/index.ts" +export default () => ({ + bootstrap() { + strapi.findOne('ap'); + // ^ TypeScript will autocomplete with "api::article.article" - strapi.findOne('ad'); - // ^ TypeScript will autocomplete with "admin::user" + strapi.findOne('ad'); + // ^ TypeScript will autocomplete with "admin::user" - strapi.findOne('api::blog.blog'); - // ^ Error, TypeScript will complain - } - }) - ``` - - - ```ts title="@strapi/strapi/document-service.ts" - import type { UID } from '@strapi/types'; - - export const findOne(uid: TUID) { - // ... + strapi.findOne('api::blog.blog'); + // ^ Error, TypeScript will complain } +}) +``` + + +```ts title="@strapi/strapi/document-service.ts" +import type { UID } from '@strapi/types'; - findOne('admin::foo'); - // ^ Valid, matches 'admin::${string}' +export const findOne(uid: TUID) { + // ... +} - findOne('plugin::bar.bar'); - // ^ Valid, matches 'plugin::${string}.${string}' +findOne('admin::foo'); +// ^ Valid, matches 'admin::${string}' - findOne('baz'); - // ^ Error, does not correspond to any content-type UID format - ``` - +findOne('plugin::bar.bar'); +// ^ Valid, matches 'plugin::${string}.${string}' + +findOne('baz'); +// ^ Error, does not correspond to any content-type UID format +``` + + +