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

feat(types): create @decorators/types package #13

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
156 changes: 145 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,41 @@ Monorepo for packages published under the [`@decorators/*`][JSR] scope on [JSR].

## Packages

#### [`@decorators/alias`][@decorators/alias]
### [`@decorators/alias`]

> Alias class members to simplify stack traces.
Creates aliases for existing class members, with support for methods, getters,
setters, auto-accessors, and fields. Static members are also supported, along
with `#private` members (in environments with support for ES2022 class fields).

> **Note**: when working with private members, the `@alias` decorator **must**
> be applied inside of the **same** enclosing class that the member is declared
> in. This is due to the way that private members are scoped in JavaScript; the

Simplifies stack traces for improved debugging, improves code readability for a
more maintainable codebase, and reduces the boilerplate typically associated
with aliasing class members in TypeScript/JavaScript.

#### Install

<img align="right" src="https://api.iconify.design/logos:deno.svg?height=3rem&width=4rem" alt="Deno" />

```sh
deno add @decorators/alias
```

[<img align="right" src="https://jsr.io/logo-square.svg" width="64" height="54" alt="JSR" />][jsr]

```sh
jsr add @decorators/alias
```

<img align="right" src="https://api.iconify.design/logos:npm.svg?height=3.666rem&width=4rem" alt="NPM">

```sh
npx jsr add @decorators/alias
```

#### Usage

```ts
import { alias } from "@decorators/alias";
Expand Down Expand Up @@ -39,20 +71,46 @@ console.assert(foo.qux === foo.bar); // OK
console.assert(foo.nurp === foo.bar); // OK
```

#### [`@decorators/bind`][@decorators/bind]
---

### [`@decorators/bind`]

Bind methods, getters, and setters to the appropriate context object, with
support for static members and inheritance.

#### Install

<img align="right" src="https://api.iconify.design/logos:deno.svg?height=3rem&width=4rem" alt="Deno" />

```sh
deno add @decorators/bind
```

[<img align="right" src="https://jsr.io/logo-square.svg" width="64" height="54" alt="JSR" />][jsr]

```sh
jsr add @decorators/bind
```

<img align="right" src="https://api.iconify.design/logos:npm.svg?height=3.666rem&width=4rem" alt="NPM">

> Bind methods, getters, and setters to the appropriate context object, with
> support for static members and inheritance.
```sh
npx jsr add @decorators/bind
```

#### Usage

```ts
import { bind } from "@decorators/bind";

class Foo {
@bind bar(): Foo {
@bind
bar(): Foo {
return this;
}

@bind static self(): typeof Foo {
@bind
static self(): typeof Foo {
return this;
}
}
Expand All @@ -64,6 +122,82 @@ console.log(bar() instanceof Foo); // true

---

### [`@decorators/types`]

Collection of type guard functions, decorator function signatures, decorator
factory signatures, and other utility types for working with both Stage 3 and
Legacy Decorators (Stage 2 / `experimentalDecorators`).

#### Install

<img align="right" src="https://api.iconify.design/logos:deno.svg?height=3rem&width=4rem" alt="Deno" />

```sh
deno add @decorators/types
```

[<img align="right" src="https://jsr.io/logo-square.svg" width="64" height="54" alt="JSR" />][jsr]

```sh
jsr add @decorators/types
```

<img align="right" src="https://api.iconify.design/logos:npm.svg?height=3.666rem&width=4rem" alt="NPM">

```sh
npx jsr add @decorators/types
```

#### Usage

```ts
import {
type AnyDecoratorArguments,
type AnyDecoratorReturn,
isDecoratorArguments,
isLegacyDecoratorArguments,
} from "@decorators/types";

function toStringTag<Args extends AnyDecoratorArguments>(
value: string,
): (...args: Args) => AnyDecoratorReturn<Args>;
// deno-lint-ignore no-explicit-any
function toStringTag(value: string): (...args: any[]) => any {
return (...args) => {
if (isDecoratorArguments(args)) {
const [target, context] = args;
if (context.kind !== "class") {
throw new TypeError(
`@toStringTag cannot decorate ${context.kind}s - it can only be used on the class itself.`,
);
}
context.addInitializer(function () {
Object.defineProperty(this.prototype, Symbol.toStringTag, {
value,
configurable: true,
});
});
} else if (isLegacyDecoratorArguments(args)) {
const [target] = args;
Object.defineProperty(target.prototype, Symbol.toStringTag, {
value,
configurable: true,
});
} else {
throw new TypeError("@toStringTag received invalid arguments");
}
};
}

// this decorator factory works in TS 4.x and 5.x without issue:
@toStringTag("Foo")
class Foo {
// ...
}
```

---

<div align="center">

##### **[MIT]** © **[Nicholas Berlette]**. <small>All rights reserved.</small>
Expand All @@ -72,14 +206,14 @@ console.log(bar() instanceof Foo); // true

</div>

[GitHub]: https://github.com/nberlette/decorators#readme "Check out all the '@decorators/*' packages over at the GitHub monorepo!"
[@decorators/alias]: https://github.com/nberlette/decorators/tree/main/packages/alias#readme "Check out '@decorators/alias' and more over at the GitHub monorepo!"
[@decorators/bind]: https://github.com/nberlette/decorators/tree/main/packages/bind#readme "Check out '@decorators/bind' and more over at the GitHub monorepo!"
[GitHub]: https://github.com/nberlette/decorators/tree/main/packages/bind#readme "Check out all the '@decorators/*' packages over at the GitHub monorepo!"
[@decorators/types]: https://github.com/nberlette/decorators/tree/main/packages/types#readme "Check out '@decorators/types' and more over at the GitHub monorepo!"
[MIT]: https://nick.mit-license.org "MIT © 2024+ Nicholas Berlette. All rights reserved."
[Nicholas Berlette]: https://github.com/nberlette "Nicholas Berlette on GitHub"
[Issues]: https://github.com/nberlette/decorators/issues "GitHub Issue Tracker for '@decorators/*' packages"
[Open an Issue]: https://github.com/nberlette/decorators/issues/new?assignees=nberlette&labels=bugs&title=%5Bbind%5D+ "Found a bug? Let's squash it!"
[Docs]: https://n.berlette.com/decorators "View @decorators API docs"
[Open an Issue]: https://github.com/nberlette/decorators/issues/new?assignees=nberlette&labels=bugs "Found a bug? Let's squash it!"
[Docs]: https://nberlette.github.io/decorators "View @decorators API docs"
[JSR]: https://jsr.io/@decorators "View @decorators/* packages on JSR"
[Stage 3 Decorators]: https://github.com/tc39/proposal-decorators "TC39 Proposal: Decorators"
[@]: https://api.iconify.design/streamline:mail-sign-at-email-at-sign-read-address.svg?width=2.5rem&height=1.4rem&color=%23fb0
5 changes: 3 additions & 2 deletions deno.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@decorators/main",
"version": "0.1.2",
"version": "0.1.3",
"tasks": {
"test": "deno test --parallel --allow-all --no-check=remote --coverage=.coverage ./packages/**/* ./internal/**/*",
"test:watch": "deno test --watch --parallel --allow-all --no-check=remote --coverage=.coverage ./packages/**/* ./internal/**/*",
Expand All @@ -27,6 +27,7 @@
"workspaces": [
"./internal",
"./packages/alias",
"./packages/bind"
"./packages/bind",
"./packages/types"
]
}
2 changes: 1 addition & 1 deletion internal/deno.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@decorators/internal",
"version": "0.1.2",
"version": "0.1.3",
"exports": {
".": "./mod.ts",
"./assert": "./assert.ts",
Expand Down
143 changes: 120 additions & 23 deletions internal/types.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,34 @@
export type As<T, U = unknown> = T extends U ? U extends T ? U : T
: [T & U] extends [never] ? U & Omit<T, keyof U>
: T & U;

export type Is<T, U> = [T] extends [never] ? [U] extends [never] ? never : U
: T extends unknown ? U
: T extends U ? T
: U;
// deno-lint-ignore-file no-explicit-any
/**
* @module types
*
* This module provides internal utility types used throughout various projects
* in the `@decorators` namespace. These types are not intended for direct use
* by end users, but are instead used internally to provide type safety and
* consistency across the various projects and modules. They're located in this
* module to avoid redundancy and improve maintainability.
*
* @see https://jsr.io/@decorators for more information and a full list of the
* available packages offered by the `@decorators` project namespace. Thanks!
*/

export const BRAND: unique symbol = Symbol("BRAND");
export type BRAND = typeof BRAND;

export const NEVER: unique symbol = Symbol("NEVER");
export type NEVER = typeof NEVER;

export interface Brand<B> {
/**
* Represents a unique brand for a type, which can be used to create nominal
* types in TypeScript and prevent type collisions. For a less-strict version
* of this type, see the {@linkcode Flavor} interface.
*
* To create a branded type, you can either use the {@linkcode Branded} helper
* type, or manually extend/intersect another type with this interface. The
* {@linkcode A} type parameter is the type that becomes the brand's value, and
* it defaults to `never`.
*/
export interface Brand<B = never> {
readonly [BRAND]: B;
}

Expand All @@ -27,29 +42,111 @@ export interface Flavor<F> {
export type Flavored<V, T, F = NEVER> = [F] extends [NEVER] ? V & Flavor<T>
: V | Flavored<T, F>;

// deno-lint-ignore ban-types
export type strings = string & {};

export type properties = Flavored<PropertyKey, "properties">;

export type KeyOf<T, Strict extends boolean = false> =
| (Strict extends true ? never : strings)
| (string & keyof T);

export type PropKeys<T, Strict extends boolean = false> =
| (Strict extends true ? never : properties)
| (PropertyKey & keyof T);

// deno-lint-ignore no-explicit-any
export type AbstractConstructor<T = any, A extends readonly unknown[] = any[]> =
abstract new (...args: A) => T;
export type AbstractConstructor<T = any, A extends readonly unknown[] = readonly any[]> = abstract new (...args: A) => T;

// deno-lint-ignore no-explicit-any
export type Constructor<T = any, A extends readonly unknown[] = any[]> = {
export type Constructor<T = any, A extends readonly unknown[] = readonly any[]> = {
new (...args: A): T;
};

export type FunctionKeys<T> = keyof {
// deno-lint-ignore no-explicit-any
[K in keyof T as T[K] extends (...args: any[]) => any ? K : never]: K;
[K in keyof T as T[K] extends (...args: any) => any ? K : never]: K;
};

/**
* Represents an accessor method's property descriptor. which may only have a
* getter and/or setter, a `configurable` flag, and an `enumerable` flag. The
* `value` and `writable` flags are not allowed.
*
* @template T The type of the accessor's value.
* @category Types
*/
export interface AccessorPropertyDescriptor<T = any> {
get?(): T;
set?(value: T): void;
configurable?: boolean;
enumerable?: boolean;
}


export type As<T, U = unknown> = T extends U ? U extends T ? U : T
: [T & U] extends [never] ? U & Omit<T, keyof U>
: T & U;

export type Is<T, U = unknown> = Or<Extract<T, U>, U>;

//T extends U ? U extends T ? U : T
// : [T & U] extends [never] ? U
// : T & U;

const _: unique symbol = Symbol("_");
type _ = typeof _;

export type strings = string & { [_]?: "string" | void };

export type numbers = number & { [_]?: "number" | void };

export type symbols = symbol & { [_]?: "symbol" | void };

export type PropertyKeys = strings | numbers | symbols;

export type ValueOf<T> = T[keyof T];

export type PropKeys<T, Strict extends boolean = false> =
| (Strict extends true ? never : properties)
| (PropertyKey & keyof T);

export type Class<
Prototype extends object | null = any,
Args extends readonly unknown[] = readonly any[],
Static extends {} = {},
> =
& Constructor<Prototype, Args>
& { readonly prototype: Prototype }
& ({} extends Static ? unknown : {
[K in keyof Static as [Static[K]] extends [never] ? never : K]:
& ThisType<Class<Prototype, Args, Static>>
& Static[K];
});

export type Or<A, B> = ([A & {}] extends [never] ? B : A) extends
infer V extends B ? V : never;

export type AbstractClass<
Prototype extends object | null = any,
Args extends readonly unknown[] = readonly any[],
Static extends {} = {},
> =
& AbstractConstructor<Prototype, Args>
& { readonly prototype: Prototype }
& (
{} extends Static ? unknown
: {
[K in keyof Static as [Static[K]] extends [never] ? never : K]:
& ThisType<AbstractClass<Prototype, Args, Static>>
& Static[K];
}
);

// deno-fmt-ignore
export type ParametersOf<T, Fallback extends readonly unknown[] = never> =
| [T] extends [never] ? Fallback
: readonly [...ParametersOfWorker<ValueOf<T>, Fallback>];

// deno-fmt-ignore
type ParametersOfWorker<T, Fallback extends readonly unknown[]> =
| T extends (...args: infer A) => any ? Readonly<A> : Fallback;

export type Voidable<T> = T | void;

export type VoidableArgument = boolean | void;

export type MaybeVoidable<T, V extends VoidableArgument = true> = (
| (void extends V ? void : T)
| ([V] extends [never] ? void : V extends true ? void : never)
) extends infer U ? U : never;
Loading