Skip to content

Commit c8c0d57

Browse files
committed
feat(core): initial code commit
1 parent 4373520 commit c8c0d57

File tree

9 files changed

+435
-0
lines changed

9 files changed

+435
-0
lines changed

.github/workflows/branches.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2+
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3+
4+
name: Branches
5+
6+
on:
7+
push:
8+
branches-ignore:
9+
- master
10+
11+
jobs:
12+
build:
13+
name: Build
14+
runs-on: ubuntu-latest
15+
16+
strategy:
17+
matrix:
18+
node-version:
19+
- 12.x
20+
- 14.x
21+
- 15.x
22+
steps:
23+
- uses: actions/checkout@v2
24+
- name: Use Node.js ${{ matrix.node-version }}
25+
uses: actions/setup-node@v1
26+
with:
27+
node-version: ${{ matrix.node-version }}
28+
- run: yarn
29+
- run: yarn build
30+
- run: yarn test
31+
env:
32+
CI: true

.github/workflows/master.yml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
2+
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3+
4+
name: Master
5+
6+
on:
7+
push:
8+
branches:
9+
- master
10+
11+
jobs:
12+
build:
13+
name: Build
14+
runs-on: ubuntu-latest
15+
strategy:
16+
matrix:
17+
node-version:
18+
- 12.x
19+
- 14.x
20+
- 15.x
21+
steps:
22+
- uses: actions/checkout@v2
23+
- name: Use Node.js ${{ matrix.node-version }}
24+
uses: actions/setup-node@v1
25+
with:
26+
node-version: ${{ matrix.node-version }}
27+
- run: yarn
28+
- run: yarn build
29+
- run: yarn test
30+
env:
31+
CI: true
32+
33+
release:
34+
name: Release
35+
runs-on: ubuntu-latest
36+
needs: build
37+
steps:
38+
- name: Checkout
39+
uses: actions/checkout@v1
40+
- name: Setup Node.js
41+
uses: actions/setup-node@v1
42+
with:
43+
node-version: 14
44+
- run: yarn
45+
- run: yarn build
46+
- run: yarn test
47+
env:
48+
CI: true
49+
- name: Coveralls
50+
uses: coverallsapp/github-action@master
51+
with:
52+
github-token: ${{ secrets.GITHUB_TOKEN }}
53+
- name: Release
54+
env:
55+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
56+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
57+
run: npx semantic-release

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules
2+
dist
3+
coverage
4+
yarn.lock

jest.config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module.exports = {
2+
preset: 'ts-jest',
3+
testEnvironment: 'node',
4+
coverageReporters: [ 'lcov', 'text', 'html' ],
5+
};

lib/index.test.ts

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import { analyzeTypes, sortTypeAnalysisResult } from './index'
2+
3+
4+
function expectEqual< T >( got: T, expected: T )
5+
{
6+
expect( got ).toStrictEqual( expected );
7+
}
8+
9+
describe( "analyzeTypes", ( ) =>
10+
{
11+
it( "no definition", ( ) =>
12+
{
13+
expectEqual(
14+
analyzeTypes( { } ),
15+
{
16+
entrypoints: [ ],
17+
cycles: [ ],
18+
all: [ ],
19+
dependencies: [ ],
20+
graph: [ ],
21+
}
22+
);
23+
} );
24+
25+
it( "no types", ( ) =>
26+
{
27+
expectEqual(
28+
analyzeTypes( { definitions: { } } ),
29+
{
30+
entrypoints: [ ],
31+
cycles: [ ],
32+
all: [ ],
33+
dependencies: [ ],
34+
graph: [ ],
35+
}
36+
);
37+
} );
38+
39+
it( "simple type", ( ) =>
40+
{
41+
expectEqual(
42+
analyzeTypes( { definitions: { User: { } } } ),
43+
{
44+
entrypoints: [ ],
45+
cycles: [ ],
46+
all: [ ],
47+
dependencies: [ ],
48+
graph: [ [ 'User', [ ] ] ],
49+
}
50+
);
51+
} );
52+
53+
it( "single recursive type", ( ) =>
54+
{
55+
const definitions = {
56+
User: {
57+
type: 'object',
58+
properties: {
59+
parent: { $ref: '#/definitions/User' },
60+
},
61+
},
62+
};
63+
expectEqual(
64+
analyzeTypes( { definitions } ),
65+
{
66+
entrypoints: [ ],
67+
cycles: [ [ 'User' ] ],
68+
all: [ 'User' ],
69+
dependencies: [ ],
70+
graph: [ [ 'User', [ 'User' ] ] ],
71+
}
72+
);
73+
} );
74+
75+
it( "complex typings", ( ) =>
76+
{
77+
const jsonSchema = {
78+
definitions: {
79+
Link: {}, // Non-cyclic but dependency of Message
80+
Subscriber: {
81+
type: 'object',
82+
properties: {
83+
user: { $ref: '#/definitions/User' },
84+
},
85+
},
86+
Message: {
87+
type: 'object',
88+
properties: {
89+
replyTo: { $ref: '#/definitions/Message' },
90+
link: { $ref: '#/definitions/Link' },
91+
subscriber: { $ref: '#/definitions/Subscriber' },
92+
},
93+
},
94+
User: {
95+
type: 'object',
96+
properties: {
97+
parent: { $ref: '#/definitions/User' },
98+
lastMessage: { $ref: '#/definitions/Message' },
99+
},
100+
},
101+
DM: {
102+
type: 'object',
103+
properties: {
104+
lastUser: { $ref: '#/definitions/User' },
105+
},
106+
},
107+
Actions: {
108+
type: 'object',
109+
properties: {
110+
dms: {
111+
type: 'array',
112+
items: { $ref: '#/definitions/DM' },
113+
},
114+
},
115+
},
116+
// Has dependencies, but nothing cyclic
117+
Product: { $ref: '#/non-local-dep/Foo' },
118+
Cart: {
119+
type: 'array',
120+
items: { $ref: '#/definitions/Product' },
121+
},
122+
}
123+
};
124+
125+
expectEqual(
126+
sortTypeAnalysisResult( analyzeTypes( jsonSchema ) ),
127+
sortTypeAnalysisResult( {
128+
entrypoints: [
129+
[ "DM" ],
130+
[ "Actions", "DM" ],
131+
],
132+
cycles: [
133+
[ 'User' ],
134+
[ 'Message' ],
135+
[ 'User', 'Message', 'Subscriber' ],
136+
],
137+
all: [ 'User', 'Message', 'DM', 'Actions', 'Subscriber' ],
138+
dependencies: [ "Link" ],
139+
graph: [
140+
[ 'Link', [ ] ],
141+
[ 'Subscriber', [ 'User' ] ],
142+
[ 'Message', [ 'Message', 'Link', 'Subscriber' ] ],
143+
[ 'User', [ 'Message', 'User' ] ],
144+
[ 'DM', [ 'User' ] ],
145+
[ 'Actions', [ 'DM' ] ],
146+
[ 'Product', [ ] ],
147+
[ 'Cart', [ 'Product' ] ],
148+
],
149+
} ),
150+
);
151+
} );
152+
} );
153+
154+
describe( "sortTypeAnalysisResult", ( ) =>
155+
{
156+
it( "should sort properly", ( ) =>
157+
{
158+
const sorted = sortTypeAnalysisResult( {
159+
all: [ ],
160+
cycles: [ ],
161+
dependencies: [ 'b', 'a' ],
162+
entrypoints: [ ],
163+
graph: [
164+
[ 'x', [ 'a', 'c', 'b' ] ],
165+
[ 'f', [ 'g', 'h' ] ],
166+
[ 'g', [ 'h', 'f', 'g' ] ],
167+
],
168+
} );
169+
170+
expect( sorted ).toStrictEqual( {
171+
all: [ ],
172+
cycles: [ ],
173+
dependencies: [ 'a', 'b' ],
174+
entrypoints: [ ],
175+
graph: [
176+
[ 'f', [ 'g', 'h' ] ],
177+
[ 'g', [ 'f', 'g', 'h' ] ],
178+
[ 'x', [ 'a', 'b', 'c' ] ],
179+
],
180+
} );
181+
} );
182+
} );

lib/index.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import * as traverse from 'json-schema-traverse'
2+
import {
3+
analyzeGraph,
4+
sortAnalysisResult,
5+
Edge,
6+
Graph,
7+
AnalysisResult,
8+
} from 'graph-cycles'
9+
10+
11+
function decodeRef( ref: string | undefined )
12+
{
13+
if ( ref == null )
14+
return undefined;
15+
16+
if ( ref.startsWith( "#/definitions/" ) )
17+
return ref.slice( 14 );
18+
19+
return undefined;
20+
}
21+
22+
interface TypeAnalysisResult extends AnalysisResult
23+
{
24+
graph: Graph;
25+
}
26+
27+
export function analyzeTypes( schema: traverse.SchemaObject )
28+
: TypeAnalysisResult
29+
{
30+
const graph = Object
31+
.keys( schema.definitions ?? { } )
32+
.map( ( type ): Edge =>
33+
{
34+
const subSchema = schema.definitions[ type ];
35+
36+
const dependencies = new Set< string >( );
37+
38+
traverse( subSchema, ( schema: traverse.SchemaObject ) =>
39+
{
40+
const ref = decodeRef( schema.$ref );
41+
if ( ref )
42+
dependencies.add( ref );
43+
} );
44+
45+
return [ type, [ ...dependencies ] ];
46+
} );
47+
48+
return { ...analyzeGraph( graph ), graph };
49+
}
50+
51+
export function sortTypeAnalysisResult( result: TypeAnalysisResult )
52+
: TypeAnalysisResult
53+
{
54+
const { graph } = result;
55+
56+
const sortedGraph =
57+
[ ...graph ]
58+
.sort( ( a, b ) => a[ 0 ].localeCompare( b[ 0 ] ) )
59+
.map( ( [ from, to ] ): Edge => [ from, [ ...to ].sort( ) ] );
60+
61+
return {
62+
...sortAnalysisResult( result ),
63+
graph: sortedGraph,
64+
};
65+
}

0 commit comments

Comments
 (0)