Skip to content

Commit 91ea4b2

Browse files
committed
chore: isolate runtime package and improve branding
1 parent d34328b commit 91ea4b2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+874
-599
lines changed

.changeset/stupid-flies-suffer.md

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@tsplus/runtime": minor
3+
"@tsplus/stdlib": minor
4+
---
5+
6+
Move Runtime to it's own Package

.vscode/settings.json

+2-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"typescript.tsdk": "node_modules/typescript/lib",
33
"typescript.preferences.importModuleSpecifier": "non-relative",
44
"typescript.enablePromptUseWorkspaceTsdk": true,
5+
"typescript.tsserver.experimental.enableProjectDiagnostics": true,
56
"editor.formatOnSave": true,
67
"eslint.format.enable": true,
78
"explorer.sortOrder": "mixed",
@@ -38,11 +39,7 @@
3839
"typescript",
3940
"typescriptreact"
4041
],
41-
"eslint.validate": [
42-
"markdown",
43-
"javascript",
44-
"typescript"
45-
],
42+
"eslint.validate": ["markdown", "javascript", "typescript"],
4643
"editor.codeActionsOnSave": {
4744
"source.fixAll.eslint": true
4845
},

package.json

+7-7
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
"changeset": "changeset",
1313
"release": "changeset publish",
1414
"clean": "ultra -r clean && rimraf tsconfig.tsbuildinfo",
15-
"build": "ultra -r build-pack",
15+
"build": "ultra -r -b build-pack",
1616
"build-all": "tsc -b tsconfig.json",
1717
"build-watch": "tsc -b tsconfig.json --watch",
1818
"circular": "yarn org:madge --ts-config ./tsconfig.madge.json --circular --no-color --no-spinner --warning packages/*/build/esm packages/*/build/test packages/*/build/examples",
@@ -39,17 +39,17 @@
3939
"@effect-ts/build-utils": "0.40.3",
4040
"@effect-ts/core": "^0.60.2",
4141
"@repo-tooling/eslint-plugin-dprint": "^0.0.4",
42-
"@tsplus/installer": "^0.0.113",
42+
"@tsplus/installer": "^0.0.117",
4343
"@types/node": "^18.0.0",
4444
"@types/rimraf": "^3.0.2",
45-
"@typescript-eslint/eslint-plugin": "^5.28.0",
46-
"@typescript-eslint/parser": "^5.28.0",
45+
"@typescript-eslint/eslint-plugin": "^5.29.0",
46+
"@typescript-eslint/parser": "^5.29.0",
4747
"babel-plugin-annotate-pure-calls": "^0.4.0",
4848
"babel-plugin-replace-import-extension": "^1.1.3",
4949
"c8": "^7.11.3",
5050
"concurrently": "^7.2.2",
5151
"cpx": "^1.5.0",
52-
"eslint": "^8.17.0",
52+
"eslint": "^8.18.0",
5353
"eslint-import-resolver-typescript": "^2.7.1",
5454
"eslint-plugin-codegen": "0.16.1",
5555
"eslint-plugin-import": "^2.26.0",
@@ -58,10 +58,10 @@
5858
"madge": "^5.0.1",
5959
"picocolors": "^1.0.0",
6060
"rimraf": "^3.0.2",
61-
"typescript": "^4.7.3",
61+
"typescript": "^4.7.4",
6262
"ultra-runner": "^3.10.5",
6363
"vite": "^2.9.12",
64-
"vitest": "0.15.1"
64+
"vitest": "0.15.2"
6565
},
6666
"resolutions": {
6767
"eslint-plugin-codegen": "patch:eslint-plugin-codegen@npm:0.16.1#.yarn/patches/eslint-plugin-codegen-npm-0.16.1-87770191cd"

packages/runtime/.babel.cjs.json

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"plugins": [
3+
[
4+
"@babel/transform-modules-commonjs"
5+
],
6+
[
7+
"annotate-pure-calls"
8+
]
9+
]
10+
}

packages/runtime/.babel.mjs.json

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"plugins": [
3+
[
4+
"replace-import-extension",
5+
{
6+
"extMapping": {
7+
".js": ".mjs"
8+
}
9+
}
10+
],
11+
[
12+
"annotate-pure-calls"
13+
]
14+
]
15+
}

packages/runtime/_examples/global.ts

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
import "@tsplus/runtime/global"
2+
import "@tsplus/stdlib/global"

packages/runtime/_examples/main.ts

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
export type Age = Positive & Int & Finite & Range<0, 250> & Brand<"Age">
2+
export const Age = Derive<Make<Age>>()
3+
4+
export type Name = string & Brand<"Name"> & Regex<`^([A-Z|a-z]*)$`>
5+
export const Name = Derive<Make<Name>>()
6+
7+
export interface Person {
8+
readonly name: Name
9+
readonly age: Maybe<Age>
10+
}
11+
export const Person = Derive<Codec<Person>>()
12+
13+
const person = Person.make({
14+
name: Name.unsafeMake("Mike"),
15+
age: Maybe.some(Age.unsafeMake(30))
16+
})
17+
18+
const decoded = Person.decodeJSON(Person.encodeJSON(person))
19+
20+
if (decoded.isRight()) {
21+
console.log(decoded.right.name)
22+
}

packages/runtime/_src/Brand.ts

+209
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
declare const validSym: unique symbol
2+
declare const namedSym: unique symbol
3+
4+
/**
5+
* @tsplus derive nominal
6+
*/
7+
export interface Brand<in out K extends string> {
8+
[namedSym]: {
9+
[k in K]: K
10+
}
11+
}
12+
13+
export declare namespace Brand {
14+
export type valid = typeof validSym
15+
export type name = typeof namedSym
16+
17+
export type Validated<A, K extends string> = A & Brand.Valid<A, K>
18+
19+
/**
20+
* @tsplus derive nominal
21+
*/
22+
export interface Valid<in out A, in out K extends string> {
23+
[validSym]: {
24+
[k in K]: A
25+
}
26+
}
27+
28+
export type IsValidated<P extends Valid<any, any>> = {
29+
[k in keyof P[Brand.valid]]: P extends P[Brand.valid][k] ? 0 : 1
30+
}[keyof P[Brand.valid]] extends 0 ? unknown : never
31+
32+
export type Unbranded<P> = P extends infer Q & Brands<P> ? Q : P
33+
export type Unnamed<P> = P extends infer Q & Names<P> ? Q : P
34+
35+
export type Brands<P> = P extends Valid<any, any> ? TypeLevel.UnionToIntersection<
36+
{
37+
[k in keyof P[Brand.valid]]: P extends P[Brand.valid][k] ? k extends string ? Valid<P[Brand.valid][k], k> : never
38+
: never
39+
}[keyof P[Brand.valid]]
40+
>
41+
: unknown
42+
43+
export type Names<P> = P extends Brand<any> ? TypeLevel.UnionToIntersection<
44+
{
45+
[k in keyof P[Brand.name]]: k extends string ? Brand<k>
46+
: never
47+
}[keyof P[Brand.name]]
48+
>
49+
: unknown
50+
51+
export interface FailedValidation {
52+
readonly _tag: "FailedValidation"
53+
readonly brands: string[]
54+
readonly message: string
55+
}
56+
57+
/**
58+
* @tsplus type Brand.MakeValidated
59+
* @tsplus derive nominal
60+
*/
61+
export interface MakeValidated<in out A> {
62+
make: (value: Unnamed<Unbranded<A>>) => Either<FailedValidation, A>
63+
unsafeMake: (value: Unnamed<Unbranded<A>>) => A
64+
}
65+
66+
/**
67+
* @tsplus type Brand.Make
68+
* @tsplus derive nominal
69+
*/
70+
export interface Make<in out A> {
71+
make: (value: Unnamed<Unbranded<A>>) => A
72+
}
73+
74+
/**
75+
* @tsplus type Brand.Validation
76+
* @tsplus derive nominal
77+
*/
78+
export interface Validation<in out A, in out K extends string> {
79+
readonly validate: (a: A) => a is A & Brand.Valid<A, K>
80+
}
81+
82+
export type ValidatedWith<X extends Validation<any, any>> = X extends Validation<infer A, infer K> ? Validated<A, K>
83+
: never
84+
85+
/**
86+
* @tsplus type Brand/Ops
87+
*/
88+
export interface Ops {
89+
readonly validation: <A, K extends string>(predicate: (a: A) => boolean) => Brand.Validation<A, K>
90+
}
91+
}
92+
93+
export function validation<A, K extends string>(predicate: (a: A) => boolean): Brand.Validation<A, K> {
94+
return {
95+
validate: (value): value is Brand.Validated<A, K> => predicate(value)
96+
}
97+
}
98+
99+
export const Brand: Brand.Ops = {
100+
validation
101+
}
102+
103+
export class FailedValidationException extends Error implements Brand.FailedValidation {
104+
readonly _tag = "FailedValidation"
105+
constructor(readonly brands: string[]) {
106+
super(`Failed Validation of brands: ${brands.join(", ")}`)
107+
}
108+
}
109+
110+
export class FailedValidationError implements Brand.FailedValidation {
111+
readonly _tag = "FailedValidation"
112+
constructor(readonly brands: string[]) {}
113+
get message() {
114+
return `Failed Validation of brands: ${this.brands.join(", ")}`
115+
}
116+
}
117+
118+
export type Regex<R extends string> = Brand.Validated<string, `Regex(${R})`>
119+
120+
/**
121+
* @tsplus derive Brand.Validation<_, _> 10
122+
*/
123+
export function deriveRegexValidation<B extends string, V extends `Regex(${string})`>(
124+
...[regexStr]: V extends `Regex(${infer R extends string})` ? [R] : never
125+
): Brand.Validation<B, V> {
126+
const r = new RegExp(regexStr)
127+
return validation((b) => r.test(b))
128+
}
129+
130+
export type Min<N extends number> = Brand.Validated<number, `Min(${N})`>
131+
export type Max<N extends number> = Brand.Validated<number, `Max(${N})`>
132+
export type Range<X extends number, Y extends number> = Min<X> & Max<Y>
133+
134+
/**
135+
* @tsplus derive Brand.Validation<_, _> 10
136+
*/
137+
export function deriveMinValidation<B extends number, V extends `Min(${number})`>(
138+
...[min]: V extends `Min(${infer N extends number})` ? [N] : never
139+
): Brand.Validation<B, V> {
140+
return validation((b) => b >= min)
141+
}
142+
143+
/**
144+
* @tsplus derive Brand.Validation<_, _> 10
145+
*/
146+
export function deriveMaxValidation<B extends number, V extends `Max(${number})`>(
147+
...[max]: V extends `Max(${infer N extends number})` ? [N] : never
148+
): Brand.Validation<B, V> {
149+
return validation((b) => b <= max)
150+
}
151+
152+
/**
153+
* @tsplus derive Brand.MakeValidated<_> 10
154+
*/
155+
export function deriveMakeMakeValidated<A>(
156+
...[brands]: [
157+
brands: A extends Brand.Valid<any, any> ? {
158+
[k in (keyof A[Brand.valid]) & string]: Brand.Validation<A[Brand.valid][k], k>
159+
}
160+
: {}
161+
]
162+
): Brand.MakeValidated<A> {
163+
const make = (value: Brand.Unnamed<Brand.Unbranded<A>>): Either<Brand.FailedValidation, A> => {
164+
const failures: string[] = []
165+
for (const brand of Object.keys(brands)) {
166+
if (!brands[brand]!.validate(value as any)) {
167+
failures.push(brand)
168+
}
169+
}
170+
if (failures.length > 0) {
171+
return Either.left(new FailedValidationError(failures))
172+
}
173+
return Either.right(value)
174+
}
175+
const unsafeMake = (value: Brand.Unnamed<Brand.Unbranded<A>>) => {
176+
const errorOrValue = make(value)
177+
if (errorOrValue.isLeft()) {
178+
throw new FailedValidationException(errorOrValue.left.brands)
179+
}
180+
return errorOrValue.right
181+
}
182+
return {
183+
make,
184+
unsafeMake
185+
}
186+
}
187+
188+
/**
189+
* @tsplus derive Brand.Make<_> 10
190+
*/
191+
export function deriveMake<A>(
192+
..._: []
193+
): Brand.Make<A> {
194+
return {
195+
make: (a) => a
196+
}
197+
}
198+
199+
/** @tsplus implicit */
200+
export const Positive = Brand.validation<number, "Positive">((n: number) => n > 0)
201+
export type Positive = Brand.ValidatedWith<typeof Positive>
202+
203+
/** @tsplus implicit */
204+
export const Int = Brand.validation<number, "Int">((n: number) => Number.isInteger(n))
205+
export type Int = Brand.ValidatedWith<typeof Int>
206+
207+
/** @tsplus implicit */
208+
export const Finite = Brand.validation<number, "Finite">((n: number) => Number.isFinite(n))
209+
export type Finite = Brand.ValidatedWith<typeof Finite>

packages/runtime/_src/Codec.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export type Codec<X> =
2+
& Decoder<X>
3+
& Encoder<X>
4+
& Guard<X>
5+
& Make<X>

0 commit comments

Comments
 (0)