1- import { Arbitrary } from '../check/arbitrary/definition/Arbitrary' ;
2- import type { Value } from '../check/arbitrary/definition/Value' ;
3- import type { Random } from '../random/generator/Random' ;
4- import { Stream } from '../stream/Stream' ;
5- import { safeMap , safePush } from '../utils/globals' ;
6- import type { DepthIdentifier } from './_internals/helpers/DepthContext' ;
7- import { createDepthIdentifier } from './_internals/helpers/DepthContext' ;
8- import type {
9- Arbitraries ,
10- Arity ,
11- EntityGraphValue ,
12- EntityRelations ,
13- ProducedLinks ,
14- UnlinkedEntities ,
15- } from './_internals/interfaces/EntityGraphTypes' ;
1+ import type { Arbitrary } from '../check/arbitrary/definition/Arbitrary' ;
2+ import type { Arbitraries , EntityGraphValue , EntityRelations } from './_internals/interfaces/EntityGraphTypes' ;
163import { unlinkedToLinkedEntitiesMapper } from './_internals/mappers/UnlinkedToLinkedEntities' ;
17- import { array } from './array' ;
18- import { integer } from './integer' ;
19- import { noBias } from './noBias' ;
20- import { option } from './option' ;
21- import { record } from './record' ;
22- import { uniqueArray } from './uniqueArray' ;
4+ import { onTheFlyLinksForEntityGraph } from './_internals/OnTheFlyLinksForEntityGraphArbitrary' ;
5+ import { unlinkedEntitiesForEntityGraph } from './_internals/UnlinkedEntitiesForEntityGraph' ;
236
24- const safeObjectCreate = Object . create ;
257const safeObjectKeys = Object . keys ;
268
279export type { EntityGraphValue , Arbitraries as EntityGraphArbitraries , EntityRelations as EntityGraphRelations } ;
@@ -40,126 +22,6 @@ export type EntityGraphContraints = {
4022 noNullPrototype ?: boolean ;
4123} ;
4224
43- // Internal class containing the implementation
44- class EntityGraphArbitrary < TEntityFields , TEntityRelations extends EntityRelations < TEntityFields > > extends Arbitrary <
45- EntityGraphValue < TEntityFields , TEntityRelations >
46- > {
47- constructor (
48- readonly arbitraries : Arbitraries < TEntityFields > ,
49- readonly relations : TEntityRelations ,
50- readonly constraints : { defaultEntities : ( keyof TEntityFields ) [ ] } & EntityGraphContraints ,
51- ) {
52- super ( ) ;
53- }
54-
55- private static computeLinkIndex (
56- arity : Arity ,
57- countInTargetType : number ,
58- currentEntityDepth : DepthIdentifier ,
59- mrng : Random ,
60- biasFactor : number | undefined ,
61- ) : number [ ] | number | undefined {
62- const linkArbitrary = noBias ( integer ( { min : 0 , max : countInTargetType } ) ) ;
63- switch ( arity ) {
64- case '0-1' :
65- return option ( linkArbitrary , { nil : undefined , depthIdentifier : currentEntityDepth } ) . generate ( mrng , biasFactor )
66- . value ;
67- case '1' :
68- return linkArbitrary . generate ( mrng , biasFactor ) . value ;
69- case 'many' : {
70- let randomUnicity = 0 ;
71- const values = uniqueArray ( linkArbitrary , {
72- depthIdentifier : currentEntityDepth ,
73- selector : ( v ) => ( v === countInTargetType ? v + ++ randomUnicity : v ) ,
74- } ) . generate ( mrng , biasFactor ) . value ;
75- let offset = 0 ;
76- return safeMap ( values , ( v ) => ( v === countInTargetType ? v + offset ++ : v ) ) ;
77- }
78- }
79- }
80-
81- generate ( mrng : Random , biasFactor : number | undefined ) : Value < EntityGraphValue < TEntityFields , TEntityRelations > > {
82- // The set of all produced links between entities.
83- const producedLinks : ProducedLinks < TEntityFields , TEntityRelations > = safeObjectCreate ( null ) ;
84- for ( const name in this . arbitraries ) {
85- producedLinks [ name ] = [ ] ;
86- }
87- // Made of any entity whose links have to be created before building the whole graph.
88- const toBeProducedEntities : { type : keyof TEntityFields ; indexInType : number ; depth : number } [ ] = [ ] ;
89- for ( const name of this . constraints . defaultEntities ) {
90- safePush ( toBeProducedEntities , { type : name , indexInType : producedLinks [ name ] . length , depth : 0 } ) ;
91- safePush ( producedLinks [ name ] , safeObjectCreate ( null ) ) ;
92- }
93-
94- // STEP I - Producing links between entities...
95- // Ideally toBeProducedEntities should be a queue, but given JavaScript built-ins arrays perform badly in queue mode,
96- // we decided to consider an always growing array that will grow up to the numer of entities before being dropped.
97- let lastTreatedEntities = - 1 ;
98- while ( ++ lastTreatedEntities < toBeProducedEntities . length ) {
99- const currentEntity = toBeProducedEntities [ lastTreatedEntities ] ;
100- const currentRelations = this . relations [ currentEntity . type ] ;
101- const currentProducedLinks = producedLinks [ currentEntity . type ] ;
102- // Create all the links going from the current entity to others
103- const currentLinks = currentProducedLinks [ currentEntity . indexInType ] ;
104- const currentEntityDepth = createDepthIdentifier ( ) ;
105- currentEntityDepth . depth = currentEntity . depth ;
106- for ( const name in currentRelations ) {
107- const relation = currentRelations [ name ] ;
108- const targetType = relation . type ;
109- const producedLinksInTargetType = producedLinks [ targetType ] ;
110- const countInTargetType = producedLinksInTargetType . length ;
111- const linkOrLinks = EntityGraphArbitrary . computeLinkIndex (
112- relation . arity ,
113- producedLinksInTargetType . length ,
114- currentEntityDepth ,
115- mrng ,
116- biasFactor ,
117- ) ;
118- currentLinks [ name ] = { type : targetType , index : linkOrLinks } ;
119- const links = linkOrLinks === undefined ? [ ] : typeof linkOrLinks === 'number' ? [ linkOrLinks ] : linkOrLinks ;
120- for ( const link of links ) {
121- if ( link >= countInTargetType ) {
122- safePush ( toBeProducedEntities , { type : targetType , indexInType : link , depth : currentEntity . depth + 1 } ) ; // indexInType should be equal to producedLinksInTargetType.length
123- safePush ( producedLinksInTargetType , safeObjectCreate ( null ) ) ;
124- }
125- }
126- }
127- }
128- // Drop any item from the array
129- toBeProducedEntities . length = 0 ;
130-
131- // STEP II - Producing entities themselves
132- const recordContraints = { noNullPrototype : this . constraints . noNullPrototype } ;
133- const recordModel : { [ K in keyof TEntityFields ] : Arbitrary < TEntityFields [ K ] [ ] > } = safeObjectCreate ( null ) ;
134- for ( const name in this . arbitraries ) {
135- const entityRecordModel = this . arbitraries [ name ] ;
136- const count = producedLinks [ name ] . length ;
137- recordModel [ name ] = array ( record ( entityRecordModel , recordContraints ) , {
138- minLength : count ,
139- maxLength : count ,
140- } ) as any ;
141- }
142- return record < UnlinkedEntities < TEntityFields > > ( recordModel )
143- . map ( ( unlinkedEntities ) => {
144- // @ts -expect-error - We probably have a fishy typing issue in `record`, as we are supposed to produce `UnlinkedEntities<TEntityFields>`
145- const safeUnlinkedEntities : UnlinkedEntities < TEntityFields > = unlinkedEntities ;
146- return unlinkedToLinkedEntitiesMapper ( safeUnlinkedEntities , producedLinks ) ;
147- } )
148- . generate ( mrng , biasFactor ) ;
149- }
150-
151- canShrinkWithoutContext ( value : unknown ) : value is EntityGraphValue < TEntityFields , TEntityRelations > {
152- return false ; // for now, we reject any shrink without any context
153- }
154-
155- shrink (
156- _value : unknown ,
157- _context : unknown | undefined ,
158- ) : Stream < Value < EntityGraphValue < TEntityFields , TEntityRelations > > > {
159- return Stream . nil ( ) ; // for now, we don't support any shrink
160- }
161- }
162-
16325/**
16426 * Generate values based on a schema. Produced values will automatically come with links between each others when requested to.
16527 *
@@ -189,5 +51,19 @@ export function entityGraph<TEntityFields, TEntityRelations extends EntityRelati
18951 constraints : EntityGraphContraints = { } ,
19052) : Arbitrary < EntityGraphValue < TEntityFields , TEntityRelations > > {
19153 const defaultEntities = safeObjectKeys ( arbitraries ) as ( keyof typeof arbitraries ) [ ] ;
192- return new EntityGraphArbitrary ( arbitraries , relations , { ...constraints , defaultEntities } ) ;
54+ const unlinkedContraints = { noNullPrototype : constraints . noNullPrototype } ;
55+
56+ return (
57+ // Step 1, Producing links between entities
58+ onTheFlyLinksForEntityGraph ( relations , defaultEntities ) . chain ( ( producedLinks ) =>
59+ // Step 2, Producing entities themselves
60+ // As the number of entities for each kind requires the links to be produced,
61+ // it has to be executed as a chained computation
62+ unlinkedEntitiesForEntityGraph ( arbitraries , ( name ) => producedLinks [ name ] . length , unlinkedContraints ) . map (
63+ ( unlinkedEntities ) =>
64+ // Step 3, Glueing links and entities together
65+ unlinkedToLinkedEntitiesMapper ( unlinkedEntities , producedLinks ) ,
66+ ) ,
67+ )
68+ ) ;
19369}
0 commit comments