Skip to content

Commit 7c9ee4b

Browse files
committed
Initial commit
0 parents  commit 7c9ee4b

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

+1633
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
node_modules
2+
dist
3+
test/compile-time/actual-output/*

package-lock.json

Lines changed: 85 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"name": "type-mapping",
3+
"version": "1.0.0",
4+
"description": "Map input data safely",
5+
"main": "dist/index.js",
6+
"types": "dist/index.d.ts",
7+
"scripts": {
8+
"test": "echo \"Error: no test specified\" && exit 1",
9+
"build": "tsc",
10+
"test-compile-time": "ts-node -P ./test/compile-time/tsconfig.json ./test/compile-time/runner.ts",
11+
"accept-compile-time": "ts-node -P ./test/compile-time/tsconfig.json ./test/compile-time/accept-actual.ts",
12+
"accept-one": "ts-node -P ./test/compile-time/tsconfig.json ./test/compile-time/accept-one.ts",
13+
"test-compile-time-interactive": "ts-node -P ./test/compile-time/tsconfig.json ./test/compile-time/interactive.ts"
14+
},
15+
"author": "anyhowstep",
16+
"license": "MIT",
17+
"devDependencies": {
18+
"ts-node": "^8.1.0",
19+
"typescript": "^3.5.0-dev.20190523"
20+
},
21+
"dependencies": {
22+
"@types/diff": "^4.0.2",
23+
"diff": "^4.0.1"
24+
}
25+
}

src/accepts-of-impl.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import {AnyTypeMapDelegate, TypeMapDelegate} from "./type-map-delegate";
2+
import {Accepts} from "./accepts";
3+
import {CanAccept} from "./can-accept";
4+
5+
export type AcceptsOfImpl<F extends AnyTypeMapDelegate> = (
6+
F extends TypeMapDelegate<infer T, any> ?
7+
(
8+
unknown extends T ?
9+
(
10+
F extends Accepts<infer T> ?
11+
[T] :
12+
F extends CanAccept<infer T> ?
13+
[T] :
14+
[unknown]
15+
) :
16+
[T]
17+
) :
18+
never
19+
);

src/accepts-of.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import {AnyTypeMapDelegate} from "./type-map-delegate";
2+
import {AcceptsOfImpl} from "./accepts-of-impl";
3+
import {UnionToIntersection} from "./type-operation";
4+
5+
export type AcceptsOf<F extends AnyTypeMapDelegate> = (
6+
Extract<
7+
UnionToIntersection<AcceptsOfImpl<F>>,
8+
[any]
9+
>[0]
10+
);

src/accepts.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/**
2+
Meant to be a subtype of `CanAccept<>`.
3+
4+
Says that the `SafeTypeMapDelegate<>`
5+
WANTS to accept certain data types.
6+
*/
7+
export interface Accepts<T> {
8+
accepts? : [T],
9+
}

src/can-accept-of-impl.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import {AnyTypeMapDelegate, TypeMapDelegate} from "./type-map-delegate";
2+
import {Accepts} from "./accepts";
3+
import {CanAccept} from "./can-accept";
4+
5+
export type CanAcceptOfImpl<F extends AnyTypeMapDelegate> = (
6+
F extends TypeMapDelegate<infer T, any> ?
7+
(
8+
unknown extends T ?
9+
(
10+
F extends CanAccept<infer T> ?
11+
[T] :
12+
F extends Accepts<infer T> ?
13+
[T] :
14+
[unknown]
15+
) :
16+
[T]
17+
) :
18+
never
19+
);

src/can-accept-of.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import {AnyTypeMapDelegate} from "./type-map-delegate";
2+
import {CanAcceptOfImpl} from "./can-accept-of-impl";
3+
import {UnionToIntersection} from "./type-operation";
4+
5+
export type CanAcceptOf<F extends AnyTypeMapDelegate> = (
6+
Extract<
7+
UnionToIntersection<CanAcceptOfImpl<F>>,
8+
[any]
9+
>[0]
10+
);

src/can-accept.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/**
2+
Helper type that says a `SafeTypeMapDelegate<>`
3+
CAN accept certain data types,
4+
and not throw an Error in general.
5+
6+
Just because the `SafeTypeMapDelegate<>`
7+
CAN accept the data type, doesn't mean
8+
it actually WANTS to accept it.
9+
*/
10+
export interface CanAccept<T> {
11+
canAccept? : [T],
12+
}

src/field.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import {SafeTypeMapDelegate} from "./safe-type-map-delegate";
2+
import {ResultOf} from "./result-of";
3+
4+
export interface IField<
5+
NameT extends string,
6+
F extends SafeTypeMapDelegate<any>
7+
> extends SafeTypeMapDelegate<ResultOf<F>> {
8+
name : NameT,
9+
}
10+
export type AnyField = (
11+
IField<any, any>
12+
);

src/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export * from "./type-map-delegate";
2+
export * from "./safe-type-map-delegate";
3+
export * from "./accepts";
4+
export * from "./can-accept";
5+
6+
export * from "./accepts-of";
7+
export * from "./can-accept-of";
8+
export * from "./result-of";

src/result-of.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import {AnyTypeMapDelegate} from "./type-map-delegate";
2+
3+
/**
4+
Gives you the return type of a `TypeMapDelegate<>`
5+
*/
6+
export type ResultOf<F extends AnyTypeMapDelegate> = (
7+
ReturnType<F>
8+
);

src/safe-type-map-delegate.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import {TypeMapDelegate} from "./type-map-delegate";
2+
3+
/**
4+
You should generally just use `SafeTypeMapDelegate<>`
5+
because it is more likely to throw an Error instead
6+
of silently mapping invalid values.
7+
*/
8+
export type SafeTypeMapDelegate<ResultT> = (
9+
TypeMapDelegate<unknown, ResultT>
10+
);
11+

src/type-map-delegate.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
A `TypeMapDelegate<>` must satisfy the following,
3+
4+
```ts
5+
deepEquals(f(x), f(f(x)))
6+
```
7+
8+
That is, if `f(0)` is `"hello"`, then `f("hello")` must be `f("hello")`.
9+
*/
10+
export interface TypeMapDelegate<AcceptsT, ResultT> {
11+
(name : string, mixed : AcceptsT) : ResultT,
12+
}
13+
export type AnyTypeMapDelegate = (
14+
TypeMapDelegate<any, any>
15+
);

src/type-operation.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export type UnionToIntersection<U> = (
2+
(
3+
U extends any ? (k: U) => void : never
4+
) extends (
5+
(k: infer I) => void
6+
) ? I : never
7+
);

test/compile-time/README.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
### Note
2+
3+
This project is meant to provide compile-time safety.
4+
5+
This means we have a few compile-time requirements,
6+
7+
+ Correct code should compile *with the right types*
8+
+ Incorrect code should not compile *with the right errors*
9+
10+
-----
11+
12+
### Correct code should compile *with the right types*
13+
14+
An example of compiling with the right types would be,
15+
16+
Given this input,
17+
18+
```ts
19+
import * as sd from "schema-decorator";
20+
import * as o from "typed-orm";
21+
22+
export const t = o.table(
23+
"someTableName",
24+
{
25+
x : sd.naturalNumber(),
26+
y : sd.naturalNumber(),
27+
}
28+
);
29+
```
30+
31+
If we get this output,
32+
33+
```ts
34+
import * as sd from "schema-decorator";
35+
import * as o from "typed-orm";
36+
37+
export declare const t: o.Table<{
38+
readonly alias: string;
39+
readonly name: string;
40+
/*snip*/
41+
```
42+
43+
Then we fail the test because `alias` and `name` should be of type `"someTableName"`, the string literal!
44+
45+
-----
46+
47+
### Incorrect code should not compile *with the right errors*
48+
49+
Given this input,
50+
51+
```ts
52+
import * as sd from "schema-decorator";
53+
import * as o from "typed-orm";
54+
55+
export const t = o.table(
56+
232,
57+
{
58+
x : sd.naturalNumber(),
59+
y : sd.naturalNumber(),
60+
}
61+
);
62+
```
63+
64+
We expect this output,
65+
66+
```json
67+
{
68+
"messageText": "Argument of type '232' is not assignable to parameter of type 'string'.",
69+
"code": 2345,
70+
"category": 1,
71+
"length": 3,
72+
"start": 232
73+
}
74+
```
75+
76+
If we get something else, we fail the test.
77+
78+
-----
79+
80+
### Implementing Tests
81+
82+
1. Write the input `.ts` file in the `input` folder
83+
2. Run `npm run test-compile-time`
84+
85+
You may get errors about expected output being missing, or the actual and expected output being different.
86+
87+
You can now do one of the following,
88+
89+
+ Fix the code, and run the test again
90+
+ Accept that this "actual" output is, in fact, correct.
91+
92+
If it is correct, run `npm run accept-compile-time`

test/compile-time/accept-actual.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import * as fs from "fs";
2+
import {
3+
actualOutputRoot,
4+
expectedOutputRoot,
5+
removeAllFilesAndDirectoriesSync,
6+
copyAllFilesAndDirectoriesSync,
7+
} from "./util";
8+
9+
if (fs.readdirSync(actualOutputRoot).length == 0) {
10+
throw new Error(`No files in actual output, have you run the test?`);
11+
}
12+
13+
removeAllFilesAndDirectoriesSync(expectedOutputRoot);
14+
copyAllFilesAndDirectoriesSync(
15+
actualOutputRoot,
16+
expectedOutputRoot,
17+
);
18+
removeAllFilesAndDirectoriesSync(actualOutputRoot);

0 commit comments

Comments
 (0)