Merge pull request #16724 from strapi/ts-support-2/type-system

This commit is contained in:
Jean-Sébastien Herbaux 2023-05-30 18:30:51 +02:00 committed by GitHub
commit 2a194c2c24
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 402 additions and 0 deletions

View File

@ -0,0 +1,8 @@
import type koa from 'koa';
export type ControllerHandler = <T>(
context: koa.ExtendableContext,
next: koa.Next
) => Promise<T | void> | T | void;
export type Controller = Record<string, ControllerHandler>;

View File

@ -0,0 +1,2 @@
export * from './controller';
export * from './service';

View File

@ -0,0 +1 @@
export type Service = Record<keyof any, unknown>;

View File

@ -1,3 +1,8 @@
export * from './attributes';
export * from './schemas';
export * from './strapi';
export * as Common from './common';
export * as Namespace from './namespace';
export * as UID from './uid';
export * as Registry from './registry';

View File

@ -0,0 +1,94 @@
import type * as Utils from '../utils';
/**
* Namespace for admin resources
*/
export type Admin = 'admin';
/**
* Namespace for strapi internal resources
*/
export type Strapi = 'strapi';
/**
* Namespace for scoped APIs resources
*/
export type API<T extends string = string> = `api${ColonsSeparator}${T}`;
/**
* Namespace for scoped plugins resources
*/
export type Plugin<T extends string = string> = `plugin${ColonsSeparator}${T}`;
/**
* Namespace for global resources
*/
export type Global = 'global';
/**
* Represents any namespace
*/
export type Any = API | Plugin | Admin | Strapi | Global;
/**
* Return a {@link Separator} based on the given {@link Any} ({@link DotSeparator} for {@link Scoped} and {@link ColonsSeparator} for regular ones)
*
* @example
* type S = GetSeparator<Admin>
* // ^ '::'
*
* type S = GetSeparator<API>
* // ^ '.'
*
* type S = GetSeparator<Admin | API>
* // ^ '.' | '::'
*/
export type GetSeparator<T extends Any = Any> = T extends Scoped
? // 'api::foo' | 'plugin::bar' => '.'
DotSeparator
: // 'admin' | 'strapi' | 'global' => '::'
ColonsSeparator;
/**
* Adds the corresponding separator (using {@link GetSeparator}) at the end of a namespace
*
* Warning: Using WithSeparator with a union type might produce undesired results as it'll distribute every matching suffix to every union member
*
* @example
* type T = WithSeparator<Admin>
* // ^ 'admin::'
*
* type T = WithSeparator<API>
* // ^ 'api::{string}.'
*
* type T = WithSeparator<Admin | API>
* // ^ 'admin::' | 'admin.' | 'api::{string}.' | 'api::{string}::'
*
* type T = WithSeparator<Admin> | WithSeparator<API>
* // ^ 'admin::' | 'api::{string}.'
*/
export type WithSeparator<N extends Any> = Utils.Suffix<N, GetSeparator<N>>;
/**
* Represents namespaces composed of an origin and a name, separated by colons
*/
export type Scoped<O extends string = string, S extends string = string> = Any &
`${O}${ColonsSeparator}${S}`;
/**
* Extract the scope from the given scoped namespace
*/
export type ExtractScope<T> = T extends `${string}${ColonsSeparator}${infer S}` ? S : never;
/**
* Extract the origin from the given scoped namespace
*/
export type ExtractOrigin<T> = T extends `${infer S}${ColonsSeparator}${string}` ? S : never;
/**
* Separators used to join the different parts of a namespace (e.g. building a uid)
*/
export type Separator = ColonsSeparator | DotSeparator;
type ColonsSeparator = '::';
type DotSeparator = '.';

View File

@ -0,0 +1,59 @@
import type * as UID from './uid';
/**
* Extract valid keys from a given registry.
*
* It looks for {@link UID.Any} by default but the search can be narrowed to any UID subset using the `U` generic.
*
* @example
* interface Registry {
* 'foo': unknown;
* 'default.foo': 'unknown';
* 'global::foo': unknown;
* 'api::foo.bar': unknown;
* }
*
* type T = Keys<Registry>;
* // ^ 'default.foo' | 'global::foo' | 'api::foo.bar'
* type T = Keys<Registry, UID.Policy>;
* // ^ 'global::foo' | 'api::foo.bar'
* type T = Keys<Registry, UID.Service>
* // ^ 'api::foo.bar'
*/
export type Keys<R, U extends UID.Any = UID.Any> = Extract<keyof R, U>;
/**
* Performs a `Q` filtering operation on the given `T` registry.
*
* `Q` needs to be a partial representation of a {@link UID.Parsed}
*
* Note: For additional filtering, the registry keys' type can be passed as the third generic.
*
* @example
* interface Registry {
* 'admin::foo': unknown;
* 'admin::bar': unknown;
* 'api::foo.bar': unknown;
* 'api::foo.baz': unknown;
* 'api::bar.foo': unknown;
* 'plugin::foo.bar': unknown;
* }
*
* type T = keyof WhereKeys<Registry, { namespace: Namespace.API }>;
* // ^ "api::foo.bar" | "api::foo.baz" | "api::bar.foo"
*
* type T = keyof WhereKeys<Registry, { name: 'bar' }>;
* // ^ "admin::bar" | "api::foo.bar" | "plugin::foo.bar"
*
* type T = keyof WhereKeys<Registry, { separator: '.' }>;
* // ^ "api::foo.bar" | "api::foo.baz" | "api::bar.foo" | 'plugin::foo.bar"
*
* type T = keyof WhereKeys<Registry, { namespace: Namespace.Plugin | Namespace.Admin }>;
* // ^ "plugin::foo.bar" | "admin::foo" | "admin::bar"
*
* type T = keyof WhereKeys<Registry, { namespace: Namespace.API; name: Utils.Includes<'b'> }>;
* // ^ "api::foo.bar" | "api::foo.baz"
*/
export type WhereKeys<T, Q extends Partial<UID.Parsed>, U extends UID.Any = UID.Any> = {
[uid in Keys<T, U> as UID.Parse<uid> extends Q ? uid : never]: T[uid];
};

View File

@ -0,0 +1,154 @@
import type * as Namespace from './namespace';
import type * as Utils from '../utils';
type StringSuffix<T extends string> = Utils.Suffix<T, string>;
/**
* Template for services' unique identifier
*/
export type Service = StringSuffix<
| Namespace.WithSeparator<Namespace.Admin>
| Namespace.WithSeparator<Namespace.API>
| Namespace.WithSeparator<Namespace.Plugin>
>;
/**
* Template for controllers' unique identifier
*/
export type Controller = StringSuffix<
| Namespace.WithSeparator<Namespace.Admin>
| Namespace.WithSeparator<Namespace.API>
| Namespace.WithSeparator<Namespace.Plugin>
>;
/**
* Template for policies' unique identifier
*/
export type Policy = StringSuffix<
| Namespace.WithSeparator<Namespace.Admin>
| Namespace.WithSeparator<Namespace.Strapi>
| Namespace.WithSeparator<Namespace.Global>
| Namespace.WithSeparator<Namespace.API>
| Namespace.WithSeparator<Namespace.Plugin>
>;
/**
* Template for middlewares' unique identifier
*/
export type Middleware = StringSuffix<
| Namespace.WithSeparator<Namespace.Admin>
| Namespace.WithSeparator<Namespace.Strapi>
| Namespace.WithSeparator<Namespace.Global>
| Namespace.WithSeparator<Namespace.API>
| Namespace.WithSeparator<Namespace.Plugin>
>;
/**
* Template for content-types' unique identifier
*/
export type ContentType = StringSuffix<
| Namespace.WithSeparator<Namespace.Admin>
| Namespace.WithSeparator<Namespace.Strapi>
| Namespace.WithSeparator<Namespace.API>
| Namespace.WithSeparator<Namespace.Plugin>
>;
/**
* Template for components' unique identifier
*
* Warning: Can cause overlap with other UID formats.
*/
export type Component = `${string}.${string}`;
/**
* Represents any UID
*/
export type Any = Service | Controller | Policy | Middleware | ContentType | Component;
/**
* Type representation of every UID component.
*
* The separator type is automatically inferred from the given namespace
*/
export interface Parsed<N extends Namespace.Any = Namespace.Any, E extends string = string> {
raw: `${N}${Namespace.GetSeparator<N>}${E}`;
namespace: N;
origin: N extends Namespace.Scoped ? Namespace.ExtractOrigin<N> : N;
scope: N extends Namespace.Scoped ? Namespace.ExtractScope<N> : never;
separator: Namespace.GetSeparator<N>;
name: E;
}
/**
* Parse a UID literal and returns a {@link Parsed} type.
*
* Warning: Using ParseUID with a union type might produce undesired results as it'll distribute every matching namespace parsing to every union member
*
* @example
* type T = Parse<'admin::foo'>
* // ^ { namespace: 'admin'; separator: '::'; name: 'foo'; }
*
* type T = Parse<'api::foo.bar'>
* // ^ { namespace: 'api::foo'; separator: '.'; name: 'bar'; }
*
* type T = Parse<'admin::foo' | 'api::foo.bar'>
* // ^ { namespace: 'admin' | 'api::foo' ; separator: '.' | '::'; name: 'foo' | 'bar' | 'foo.bar'; }
*/
export type Parse<U extends Any> = ExtractNamespace<U> extends infer B extends Namespace.Any
? Namespace.GetSeparator<B> extends infer S extends Namespace.Separator
? U extends `${infer N extends B}${S}${infer E extends string}`
? Parsed<N, E>
: never
: never
: never;
/**
* Determines if the UID's namespace matches the given one.
*
* It returns N (the {@link Namespace.Any} literal) if there is a match, never otherwise.
*
* @example
* type T = EnsureNamespaceMatches<'admin::foo', Namespace.Admin>
* // ^ Namespace.Admin
* @example
* type T = EnsureNamespaceMatches<'foo.bar', Namespace.API>
* // ^ never
* @example
* type T = EnsureNamespaceMatches<'api::foo.bar', Namespace.Plugin>
* // ^ never
*/
export type EnsureNamespaceMatches<U extends Any, N extends Namespace.Any> = U extends StringSuffix<
Namespace.WithSeparator<N>
>
? N
: never;
/**
* Get parsed properties from a given raw UID
*/
export type Get<U extends Any, P extends keyof Parsed> = Parse<U>[P];
/**
* Pick parsed properties from a given raw UID
*/
export type Select<U extends Any, P extends keyof Parse<U>> = Pick<Parse<U>, P>;
/**
* Extract the namespace literal from a given UID.
*
* @example
* type T = ExtractNamespace<'admin::foo'>
* // ^ Namespace.Admin
* @example
* type T = ExtractNamespace<'api::foo.bar'>
* // ^ Namespace.API
* @example
* type T = ExtractNamespace<'admin::foo' | 'api::foo.bar'>
* // ^ Namespace.Admin | Namespace.API
*/
export type ExtractNamespace<U extends Any> =
| EnsureNamespaceMatches<U, Namespace.Global>
| EnsureNamespaceMatches<U, Namespace.Admin>
| EnsureNamespaceMatches<U, Namespace.Strapi>
| EnsureNamespaceMatches<U, Namespace.API>
| EnsureNamespaceMatches<U, Namespace.Plugin>;

View File

@ -1,4 +1,7 @@
// Exports from core should already be modules
export * from './core';
export * as utils from './utils';
export * as factories from './factories';
export * as Shared from './shared';

View File

@ -0,0 +1 @@
export * from './registries';

View File

@ -0,0 +1,29 @@
/**
* Shared service registry
*/
export interface Services {}
/**
* Shared controller registry
*/
export interface Controllers {}
/**
* Shared policy registry
*/
export interface Policies {}
/**
* Shared middleware registry
*/
export interface Middlewares {}
/**
* Shared content-types registry
*/
export interface ContentTypes {}
/**
* Shared component registry
*/
export interface Components {}

View File

@ -4,6 +4,52 @@
*
* */
/**
* Alias for any literal type (useful for template string parameters)
*/
export type Literal = string | number | bigint | boolean;
/**
* Used to check if a string includes a given literal
*/
export type Includes<S extends Literal> = `${string}${S}${string}`;
/**
* Used to make sure the given string is not empty
*/
export type NonEmpty<T extends string> = T extends '' ? never : T;
/**
* Split the given string into a tuple using the given `S` literal
*/
export type Split<T extends string, S extends Literal> = T extends `${infer A}${S}${infer B}`
? [A, ...Split<B, S>]
: T extends ''
? []
: [T];
/**
* Aggregate the given tuple into a string, separated by the given `S` literal
*/
export type Join<T extends unknown[], S extends Literal> = T extends [
infer F extends Literal,
...infer R
]
? R['length'] extends 0
? F
: `${F}${S}${Join<R, S>}`
: never;
/**
* Add a literal suffix (`S`) at the end of the given string
*/
export type Suffix<T extends string, S extends Literal> = `${T}${S}`;
/**
* Add a literal prefix (`S`) at the beginning of the given string
*/
export type Prefix<T extends string, S extends Literal> = `${S}${T}`;
/**
*
* Extract the array values into an union type