Skip to content

Commit

Permalink
Merge 68ab602 into e42e41b
Browse files Browse the repository at this point in the history
  • Loading branch information
ForbesLindesay committed Mar 18, 2022
2 parents e42e41b + 68ab602 commit f0220c8
Show file tree
Hide file tree
Showing 37 changed files with 3,768 additions and 2,193 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/rollingversions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,15 @@ jobs:
strategy:
matrix:
node-version: [10.x, 12.x, 14.x]
typescript-version: ['3.9.7', '4.0.2']
# typescript-version: ['4.0.2']

steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: yarn install --frozen-lockfile
- run: yarn add -D typescript@${{ matrix.typescript-version }}
# - run: yarn add -D typescript@${{ matrix.typescript-version }}
- run: yarn build
- run: yarn test

Expand Down
7 changes: 4 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,23 @@ jobs:
strategy:
matrix:
node-version: [10.x, 12.x, 14.x]
typescript-version: ['3.9.7', '4.0.2']
# typescript-version: ['4.0.2']

steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- run: yarn install --frozen-lockfile
- run: yarn add -D typescript@${{ matrix.typescript-version }}
# - run: yarn add -D typescript@${{ matrix.typescript-version }}
- run: yarn build
- run: yarn test
- name: Coveralls Parallel
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.github_token }}
flag-name: node-${{ matrix.node-version }}-ts-${{ matrix.typescript-version }}
flag-name: node-${{ matrix.node-version }}
# flag-name: node-${{ matrix.node-version }}-ts-${{ matrix.typescript-version }}
parallel: true

finish:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ npm-debug.log*
.DS_Store

.size-snapshot.json
funtypes-0.0.0.tgz
10 changes: 0 additions & 10 deletions .vscode/settings.json

This file was deleted.

15 changes: 0 additions & 15 deletions .vscode/tasks.json

This file was deleted.

33 changes: 25 additions & 8 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,34 +1,51 @@
{
"name": "funtypes",
"version": "5.0.1",
"version": "0.0.0",
"description": "Runtime validation for static types",
"main": "./lib/index.js",
"module": "./lib/index.mjs",
"types": "./lib/index.d.ts",
"sideEffects": false,
"exports": {
"./readonly": {
"types": "./lib/readonly.d.ts",
"import": "./lib/readonly.mjs",
"default": "./lib/readonly.js"
},
".": {
"types": "./lib/index.d.ts",
"import": "./lib/index.mjs",
"default": "./lib/index.js"
}
},
"typesVersions": {
"*": {
"readonly": [
"lib/readonly.d.ts"
]
}
},
"scripts": {
"postbuild": "tsc --noEmit && rimraf lib/**/*.spec.*",
"build": "rollup -c",
"format": "node scripts/format.js",
"test": "jest $([ \"$CI\" = true ] && echo --coverage || echo --watch)",
"coveralls": "cat ./coverage/lcov.info | ./node_modules/.bin/coveralls",
"typecheck": "tsc --noEmit --watch"
},
"author": "Thomas Crockett",
"license": "MIT",
"devDependencies": {
"@types/jest": "24.0.18",
"coveralls": "^3.0.6",
"jest": "24.9.0",
"prettier": "^2.1.1",
"@types/jest": "^27.4.1",
"jest": "^27.5.1",
"prettier": "^2.6.0",
"rollup": "^2.26.11",
"rollup-plugin-prettier": "^2.1.0",
"rollup-plugin-size-snapshot": "^0.12.0",
"rollup-plugin-terser": "^7.0.2",
"rollup-plugin-typescript2": "^0.27.2",
"ts-jest": "^24.1.0",
"ts-jest": "^27.1.3",
"type-assertions": "^1.1.0",
"typescript": "4.0.2"
"typescript": "^4.6.2"
},
"keywords:": [
"runtime",
Expand Down
5 changes: 3 additions & 2 deletions rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,19 @@ import pkg from './package.json';
export default {
input: {
index: 'src/index.ts',
// contracts: 'src/contracts.ts',
readonly: 'src/readonly.ts',
},
output: [
{
dir: 'lib/',
entryFileNames: '[name].js',
chunkFileNames: 'chunk-[hash].js',
format: 'cjs',
},
{
dir: 'lib/',
entryFileNames: '[name].mjs',
chunkFileNames: '[name]-[hash].mjs',
chunkFileNames: 'chunk-[hash].mjs',
format: 'es',
},
],
Expand Down
126 changes: 126 additions & 0 deletions scripts/test-import.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
const { spawnSync } = require('child_process');
const { mkdtempSync, writeFileSync, readdirSync, mkdirSync } = require('fs');
const { tmpdir } = require('os');
const { join, resolve, relative } = require('path');

console.info(`$ npm pack`);
inheritExit(spawnSync(`npm`, [`pack`], { cwd: join(__dirname, `..`), stdio: `inherit` }));

const OUTPUTS = [
{
name: `test.cjs`,
header: [
`const assert = require('assert');`,
`const t = require('funtypes');`,
`const r = require('funtypes/readonly');`,
],
},
{
name: `test.mjs`,
header: [
`import assert from 'assert';`,
`import * as t from 'funtypes';`,
`import * as r from 'funtypes/readonly';`,
],
},
];

const assertions = [
...[`Readonly`, `Object`, `Record`].flatMap(n => [
`assert(typeof t.${n} === 'function');`,
`assert(typeof r.${n} === 'function');`,
]),
`assert(t.Object({}).isReadonly === false)`,
`assert(r.Object({}).isReadonly === true)`,
];

const dir = mkdtempSync(join(tmpdir(), `funtypes`));
for (const { name, header } of OUTPUTS) {
writeFileSync(
join(dir, name),
[...header, ``, ...assertions, ``, `console.log("✅ ${name} Import Tests Passed")`, ``].join(
`\n`,
),
);
}

mkdirSync(join(dir, `src`));
writeFileSync(
join(dir, `src`, `test.ts`),
[
`const assert = require('assert');`,
`import * as t from 'funtypes';`,
`import * as r from 'funtypes/readonly';`,
``,
`const schemaA = t.Object({value: t.String});`,
`const schemaB = r.Object({value: t.String});`,
``,
`const valueA = schemaA.parse({value: 'hello world'});`,
`valueA.value = 'updated value';`,
``,
`const valueB = schemaB.parse({value: 'hello world'});`,
`// @ts-expect-error - valueB.value is readonly`,
`valueB.value = 'updated value';`,
``,
`valueA.value = valueB.value`,
``,
...assertions,
``,
`console.log("✅ TypeScript Import Tests Passed")`,
``,
].join(`\n`),
);

writeFileSync(
join(dir, `tsconfig.json`),
JSON.stringify({
compilerOptions: {
module: 'CommonJS',
outDir: 'lib',
noImplicitAny: true,
skipLibCheck: false,
strict: true,
isolatedModules: true,
},
include: ['src'],
}) + `\n`,
);

writeFileSync(
join(dir, `package.json`),
JSON.stringify({
name: 'funtypes-test-import',
private: true,
dependencies: {
'@types/node': '^17.0.21',
typescript: '4.0.2',
},
scripts: {
typecheck: 'tsc --build',
},
}) + `\n`,
);

console.info(`$ npm install`);
inheritExit(spawnSync(`npm`, [`install`], { cwd: dir, stdio: `inherit` }));

const packPath = relative(
join(dir, `package.json`),
resolve(join(__dirname, `..`, `funtypes-0.0.0.tgz`)),
);
console.info(`$ npm install ${packPath}`);
inheritExit(spawnSync(`npm`, [`install`, packPath], { cwd: dir, stdio: `inherit` }));

for (const { name } of OUTPUTS) {
console.info(`$ node ${join(dir, name)}`);
inheritExit(spawnSync(`node`, [join(dir, name)], { cwd: dir, stdio: `inherit` }));
}

console.info(`$ npm run typecheck`);
inheritExit(spawnSync(`npm`, [`run`, `typecheck`], { cwd: dir, stdio: `inherit` }));
console.info(`$ node lib/test.js`);
inheritExit(spawnSync(`node`, [`lib/test.js`], { cwd: dir, stdio: `inherit` }));

function inheritExit(proc) {
if (proc.status !== 0) process.exit(proc.status);
}
64 changes: 33 additions & 31 deletions src/asynccontract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,38 +20,40 @@ export function AsyncContract<A extends [any, ...any[]] | [], Z>(
returnType: RuntypeBase<Z>,
): AsyncContract<A, Z> {
return {
enforce: (f: (...args: any[]) => any) => (...args: any[]) => {
if (args.length < argTypes.length) {
return Promise.reject(
new ValidationError({
message: `Expected ${argTypes.length} arguments but only received ${args.length}`,
}),
);
}
const visited: OpaqueVisitedState = createVisitedState();
for (let i = 0; i < argTypes.length; i++) {
const result = innerValidate(argTypes[i], args[i], visited);
if (result.success) {
args[i] = result.value;
} else {
return Promise.reject(new ValidationError(result));
enforce:
(f: (...args: any[]) => any) =>
(...args: any[]) => {
if (args.length < argTypes.length) {
return Promise.reject(
new ValidationError({
message: `Expected ${argTypes.length} arguments but only received ${args.length}`,
}),
);
}
}
const returnedPromise = f(...args);
if (!(returnedPromise instanceof Promise)) {
return Promise.reject(
new ValidationError({
message: `Expected function to return a promise, but instead got ${returnedPromise}`,
}),
);
}
return returnedPromise.then(value => {
const result = innerGuard(returnType, value, createGuardVisitedState());
if (result) {
throw new ValidationError(result);
const visited: OpaqueVisitedState = createVisitedState();
for (let i = 0; i < argTypes.length; i++) {
const result = innerValidate(argTypes[i], args[i], visited, false);
if (result.success) {
args[i] = result.value;
} else {
return Promise.reject(new ValidationError(result));
}
}
return value;
});
},
const returnedPromise = f(...args);
if (!(returnedPromise instanceof Promise)) {
return Promise.reject(
new ValidationError({
message: `Expected function to return a promise, but instead got ${returnedPromise}`,
}),
);
}
return returnedPromise.then(value => {
const result = innerGuard(returnType, value, createGuardVisitedState(), false);
if (result) {
throw new ValidationError(result);
}
return value;
});
},
};
}

0 comments on commit f0220c8

Please sign in to comment.