Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: contributor guide for type system concepts #20120

Merged
merged 30 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
834406f
docs: add comments to the type-system types for clarity
Convly Apr 9, 2024
fb008ec
Merge branch 'v5/tsdoc/ts-doc-as-markdown' into v5/tsdoc/init-doc-mai…
Convly Apr 9, 2024
469f319
refactor: update import statements and usage in schema.ts
Convly Apr 9, 2024
35470f7
Merge remote-tracking branch 'origin/v5/tsdoc/init-doc-main-types' in…
Convly Apr 9, 2024
31082b8
docs: add new contributor guide for the type system and its philosophy
Convly Apr 10, 2024
2de49c3
docs: add documentation for type system concepts
Convly Apr 15, 2024
0ae41a4
Merge branch 'v5/main' of github.com:strapi/strapi into v5/tsdoc/conc…
Convly Apr 15, 2024
13297ea
docs: update docs/docs/guides/05-type-system/02-concepts/01-schema.mdx
Convly Apr 16, 2024
40fa359
docs: update docs/docs/guides/05-type-system/02-concepts/01-schema.mdx
Convly Apr 16, 2024
3a8070c
docs: update docs/docs/guides/05-type-system/02-concepts/01-schema.mdx
Convly Apr 16, 2024
8718369
docs: update docs/docs/guides/05-type-system/02-concepts/02-uid.mdx
Convly Apr 16, 2024
d08de91
docs: update docs/docs/guides/05-type-system/02-concepts/02-uid.mdx
Convly Apr 16, 2024
08db027
docs: update docs/docs/guides/05-type-system/02-concepts/02-uid.mdx
Convly Apr 16, 2024
4b92680
docs: update docs/docs/guides/05-type-system/02-concepts/03-public-re…
Convly Apr 16, 2024
84dc625
docs: update docs/docs/guides/05-type-system/02-concepts/03-public-re…
Convly Apr 16, 2024
467689c
docs: update docs/docs/guides/05-type-system/02-concepts/03-public-re…
Convly Apr 17, 2024
8e1a4e2
docs: update docs/docs/guides/05-type-system/02-concepts/03-public-re…
Convly Apr 17, 2024
7709fae
docs: update docs/docs/guides/05-type-system/02-concepts/03-public-re…
Convly Apr 17, 2024
c7d9de3
docs: update docs/docs/guides/05-type-system/02-concepts/03-public-re…
Convly Apr 17, 2024
748fd44
docs: update docs/docs/guides/05-type-system/02-concepts/03-public-re…
Convly Apr 17, 2024
c7a21d3
docs: update docs/docs/guides/05-type-system/02-concepts/02-uid.mdx
Convly Apr 17, 2024
0888a0e
docs: update docs/docs/guides/05-type-system/02-concepts/02-uid.mdx
Convly Apr 17, 2024
17ff41e
docs: update docs/docs/guides/05-type-system/02-concepts/01-schema.mdx
Convly Apr 17, 2024
77b4cdd
docs: update docs/docs/guides/05-type-system/02-concepts/01-schema.mdx
Convly Apr 17, 2024
f07976e
docs: update docs/docs/guides/05-type-system/02-concepts/03-public-re…
Convly Apr 17, 2024
e9cae72
docs: clarify distinction between database models and raw schema defi…
Convly Apr 17, 2024
0e918fd
Merge branch 'v5/tsdoc/concepts' of github.com:strapi/strapi into v5/…
Convly Apr 17, 2024
5a1a695
Merge branch 'v5/main' of github.com:strapi/strapi into v5/tsdoc/conc…
Convly Apr 18, 2024
ffa291d
docs: update docs/docs/guides/05-type-system/02-concepts/03-public-re…
Convly Apr 18, 2024
642e1bf
docs: update docs/docs/guides/05-type-system/02-concepts/02-uid.mdx
Convly Apr 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
141 changes: 141 additions & 0 deletions docs/docs/guides/05-type-system/02-concepts/01-schema.mdx
Original file line number Diff line number Diff line change
@@ -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 -- "<code>modelType: contentType</code>" ---- ContentTypeSchema
Schema -- "<code>modelType: component</code>" ---- ComponentSchema
ContentTypeSchema -- "<code>kind: collectionType</code>" ---- CollectionTypeSchema
ContentTypeSchema -- "<code>kind: singleType</code>" ---- 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'

<Tabs>
<TabItem value="public" label="Public" default>
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); // ✅
```
</TabItem>
<TabItem value="internal" label="Internal">
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); // ✅
```
</TabItem>
</Tabs>
188 changes: 188 additions & 0 deletions docs/docs/guides/05-type-system/02-concepts/02-uid.mdx
Original file line number Diff line number Diff line change
@@ -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: `<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::<scope>` | Represent a resource present in the `<scope>` API |
| Plugin | `plugin::<scope>` | Represent a resource present in the `<scope>` 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::<scope>`, `plugin::<scope>`) and components (`<category>`)
- `::` 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::<scope>.<name>` | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| `plugin::<scope>.<name>` | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| `<category>.<name>` | :x: | :white_check_mark: | :x: | :x: | :x: | :x: |
| `strapi::<name>` | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark: | :x: | :x: |
| `admin::<name>` | :white_check_mark: | :x: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| `global::<name>` | :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...).
Convly marked this conversation as resolved.
Show resolved Hide resolved
Convly marked this conversation as resolved.
Show resolved Hide resolved
:::

#### 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<T extends UID.ContentType>(uid: T): Data.ContentType<T>;
Convly marked this conversation as resolved.
Show resolved Hide resolved
```

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<T>
>(uid: T, fields: F[]): Data.ContentType<T>;
```

:::tip
You may have noticed that we're using the inferred UID type (`T`) to reference both:
- An entity (`Data.Entity<T>`)
- A schema (`Schema.AttributeNames<T>`)

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.
:::